From d330fa689f5332480a9fc1e666cae6f8ec2b9187 Mon Sep 17 00:00:00 2001 From: Qianwen Shi Date: Thu, 21 May 2026 14:08:35 +0800 Subject: [PATCH 1/7] Prepare for Cloud Agent execution --- .github/modernize/ccacontext/.ccaskills | 68 + .github/modernize/ccacontext/cleanup.sh | 50 + .github/skills/api-service-contracts/SKILL.md | 210 +++ .github/skills/architecture-diagram/SKILL.md | 190 +++ .github/skills/assessment/SKILL.md | 173 ++ .github/skills/business-workflows/SKILL.md | 199 +++ .../skills/configuration-inventory/SKILL.md | 205 +++ .../create-dotnet-upgrade-plan/SKILL.md | 143 ++ .../dotnet-upgrade-guideline.md | 43 + .../tasks-schema.json | 349 ++++ .../upgrade-plan-template.md | 42 + .../skills/create-java-upgrade-plan/SKILL.md | 58 + .../java-upgrade-guideline.md | 44 + .../tasks-schema.json | 349 ++++ .../upgrade-plan-template.md | 51 + .../skills/create-modernization-plan/SKILL.md | 129 ++ .../dotnet-upgrade-guideline.md | 43 + .../infra-plan-template.md | 48 + .../java-upgrade-guideline.md | 44 + .../plan-template.md | 70 + .../questionnaire.md | 19 + .../security-plan-template.md | 26 + .../supported-patterns-dotnet.md | 50 + .../supported-patterns-java.md | 88 + .../tasks-schema.json | 349 ++++ .../skills/cve-known-vulnerabilities/SKILL.md | 201 +++ .github/skills/cwe-code-quality/SKILL.md | 153 ++ .../cwe-concurrency-synchronization/SKILL.md | 113 ++ .../skills/cwe-credentials-secrets/SKILL.md | 109 ++ .../skills/cwe-file-path-security/SKILL.md | 109 ++ .github/skills/cwe-injection-attacks/SKILL.md | 137 ++ .github/skills/cwe-memory-safety/SKILL.md | 149 ++ .github/skills/data-architecture/SKILL.md | 227 +++ .github/skills/dependency-map/SKILL.md | 180 +++ .../skills/dotnet-upgrade-assessment/SKILL.md | 149 ++ .../skills/execute-java-upgrade-task/SKILL.md | 431 +++++ .../execute-modernization-task/SKILL.md | 53 + .github/skills/execute-upgrade-task/SKILL.md | 23 + .github/skills/fact-application-name/SKILL.md | 112 ++ .github/skills/fact-application-port/SKILL.md | 96 ++ .github/skills/fact-application-type/SKILL.md | 133 ++ .../skills/fact-architecture-pattern/SKILL.md | 218 +++ .github/skills/fact-base-image/SKILL.md | 102 ++ .../fact-communication-protocols/SKILL.md | 118 ++ .../fact-compliance-requirements/SKILL.md | 131 ++ .github/skills/fact-container-engine/SKILL.md | 108 ++ .../skills/fact-container-version/SKILL.md | 99 ++ .../skills/fact-data-classification/SKILL.md | 151 ++ .../fact-embedded-language-usage/SKILL.md | 277 ++++ .../fact-environment-variables/SKILL.md | 105 ++ .../fact-external-dependencies/SKILL.md | 124 ++ .../skills/fact-external-services/SKILL.md | 93 ++ .../fact-hardware-requirements/SKILL.md | 115 ++ .github/skills/fact-health-checks/SKILL.md | 110 ++ .github/skills/fact-image-layers/SKILL.md | 102 ++ .github/skills/fact-image-size/SKILL.md | 121 ++ .../fact-language-dependencies/SKILL.md | 104 ++ .../fact-licensing-information/SKILL.md | 119 ++ .../skills/fact-multi-stage-build/SKILL.md | 92 ++ .github/skills/fact-network-settings/SKILL.md | 90 ++ .github/skills/fact-operating-system/SKILL.md | 126 ++ .../skills/fact-orchestration-tool/SKILL.md | 96 ++ .github/skills/fact-profile-settings/SKILL.md | 332 ++++ .github/skills/fact-resource-limits/SKILL.md | 103 ++ .../skills/fact-runtime-environment/SKILL.md | 107 ++ .../fact-security-implementation/SKILL.md | 142 ++ .../skills/fact-service-definition/SKILL.md | 108 ++ .../skills/fact-servlet-container/SKILL.md | 310 ++++ .../fact-startup-instrumentation/SKILL.md | 371 +++++ .github/skills/fact-system-packages/SKILL.md | 99 ++ .../skills/fact-testing-framework/SKILL.md | 139 ++ .../skills/fact-version-information/SKILL.md | 109 ++ .github/skills/fact-volume-mounts/SKILL.md | 96 ++ .github/skills/fact-xml-configs/SKILL.md | 298 ++++ .github/skills/generate-report-html/SKILL.md | 54 + .../generate-report-html.py | 1410 +++++++++++++++++ .github/skills/generate-report-md/SKILL.md | 54 + .../generate-report-md/generate-report-md.py | 579 +++++++ .github/skills/integration-tests/SKILL.md | 324 ++++ .../references/azure-auth-strategies.md | 189 +++ .../azure-servicebus-testcontainers.md | 688 ++++++++ .../azure-storage-testcontainers.md | 276 ++++ .../references/layer1-local-integration.md | 358 +++++ .../layer2-runner-script-templates.md | 287 ++++ .../references/layer2-smoke-tests.md | 261 +++ .../references/layer3-azure-integration.md | 194 +++ .../layer4-behavioral-comparison.md | 242 +++ .github/skills/rearchitect/SKILL.md | 126 ++ .../skills/reconcile-tasks-with-plan/SKILL.md | 46 + .../repository-dependency-graph/SKILL.md | 250 +++ .github/skills/rulebook-create/SKILL.md | 89 ++ .../rulebook-create/charter-template.md | 36 + .../rulebook-create/policies-template.md | 70 + .../references/charter-default.md | 59 + .../references/policies-default.md | 123 ++ .../references/targets-default.md | 73 + .../rulebook-create/targets-template.md | 35 + .../skills/security-assessment-merge/SKILL.md | 318 ++++ .../SKILL.md | 37 + .../validate-rulebook-compliance/SKILL.md | 31 + .../validate-rulebook-evidence/SKILL.md | 32 + 101 files changed, 16471 insertions(+) create mode 100644 .github/modernize/ccacontext/.ccaskills create mode 100644 .github/modernize/ccacontext/cleanup.sh create mode 100644 .github/skills/api-service-contracts/SKILL.md create mode 100644 .github/skills/architecture-diagram/SKILL.md create mode 100644 .github/skills/assessment/SKILL.md create mode 100644 .github/skills/business-workflows/SKILL.md create mode 100644 .github/skills/configuration-inventory/SKILL.md create mode 100644 .github/skills/create-dotnet-upgrade-plan/SKILL.md create mode 100644 .github/skills/create-dotnet-upgrade-plan/dotnet-upgrade-guideline.md create mode 100644 .github/skills/create-dotnet-upgrade-plan/tasks-schema.json create mode 100644 .github/skills/create-dotnet-upgrade-plan/upgrade-plan-template.md create mode 100644 .github/skills/create-java-upgrade-plan/SKILL.md create mode 100644 .github/skills/create-java-upgrade-plan/java-upgrade-guideline.md create mode 100644 .github/skills/create-java-upgrade-plan/tasks-schema.json create mode 100644 .github/skills/create-java-upgrade-plan/upgrade-plan-template.md create mode 100644 .github/skills/create-modernization-plan/SKILL.md create mode 100644 .github/skills/create-modernization-plan/dotnet-upgrade-guideline.md create mode 100644 .github/skills/create-modernization-plan/infra-plan-template.md create mode 100644 .github/skills/create-modernization-plan/java-upgrade-guideline.md create mode 100644 .github/skills/create-modernization-plan/plan-template.md create mode 100644 .github/skills/create-modernization-plan/questionnaire.md create mode 100644 .github/skills/create-modernization-plan/security-plan-template.md create mode 100644 .github/skills/create-modernization-plan/supported-patterns-dotnet.md create mode 100644 .github/skills/create-modernization-plan/supported-patterns-java.md create mode 100644 .github/skills/create-modernization-plan/tasks-schema.json create mode 100644 .github/skills/cve-known-vulnerabilities/SKILL.md create mode 100644 .github/skills/cwe-code-quality/SKILL.md create mode 100644 .github/skills/cwe-concurrency-synchronization/SKILL.md create mode 100644 .github/skills/cwe-credentials-secrets/SKILL.md create mode 100644 .github/skills/cwe-file-path-security/SKILL.md create mode 100644 .github/skills/cwe-injection-attacks/SKILL.md create mode 100644 .github/skills/cwe-memory-safety/SKILL.md create mode 100644 .github/skills/data-architecture/SKILL.md create mode 100644 .github/skills/dependency-map/SKILL.md create mode 100644 .github/skills/dotnet-upgrade-assessment/SKILL.md create mode 100644 .github/skills/execute-java-upgrade-task/SKILL.md create mode 100644 .github/skills/execute-modernization-task/SKILL.md create mode 100644 .github/skills/execute-upgrade-task/SKILL.md create mode 100644 .github/skills/fact-application-name/SKILL.md create mode 100644 .github/skills/fact-application-port/SKILL.md create mode 100644 .github/skills/fact-application-type/SKILL.md create mode 100644 .github/skills/fact-architecture-pattern/SKILL.md create mode 100644 .github/skills/fact-base-image/SKILL.md create mode 100644 .github/skills/fact-communication-protocols/SKILL.md create mode 100644 .github/skills/fact-compliance-requirements/SKILL.md create mode 100644 .github/skills/fact-container-engine/SKILL.md create mode 100644 .github/skills/fact-container-version/SKILL.md create mode 100644 .github/skills/fact-data-classification/SKILL.md create mode 100644 .github/skills/fact-embedded-language-usage/SKILL.md create mode 100644 .github/skills/fact-environment-variables/SKILL.md create mode 100644 .github/skills/fact-external-dependencies/SKILL.md create mode 100644 .github/skills/fact-external-services/SKILL.md create mode 100644 .github/skills/fact-hardware-requirements/SKILL.md create mode 100644 .github/skills/fact-health-checks/SKILL.md create mode 100644 .github/skills/fact-image-layers/SKILL.md create mode 100644 .github/skills/fact-image-size/SKILL.md create mode 100644 .github/skills/fact-language-dependencies/SKILL.md create mode 100644 .github/skills/fact-licensing-information/SKILL.md create mode 100644 .github/skills/fact-multi-stage-build/SKILL.md create mode 100644 .github/skills/fact-network-settings/SKILL.md create mode 100644 .github/skills/fact-operating-system/SKILL.md create mode 100644 .github/skills/fact-orchestration-tool/SKILL.md create mode 100644 .github/skills/fact-profile-settings/SKILL.md create mode 100644 .github/skills/fact-resource-limits/SKILL.md create mode 100644 .github/skills/fact-runtime-environment/SKILL.md create mode 100644 .github/skills/fact-security-implementation/SKILL.md create mode 100644 .github/skills/fact-service-definition/SKILL.md create mode 100644 .github/skills/fact-servlet-container/SKILL.md create mode 100644 .github/skills/fact-startup-instrumentation/SKILL.md create mode 100644 .github/skills/fact-system-packages/SKILL.md create mode 100644 .github/skills/fact-testing-framework/SKILL.md create mode 100644 .github/skills/fact-version-information/SKILL.md create mode 100644 .github/skills/fact-volume-mounts/SKILL.md create mode 100644 .github/skills/fact-xml-configs/SKILL.md create mode 100644 .github/skills/generate-report-html/SKILL.md create mode 100644 .github/skills/generate-report-html/generate-report-html.py create mode 100644 .github/skills/generate-report-md/SKILL.md create mode 100644 .github/skills/generate-report-md/generate-report-md.py create mode 100644 .github/skills/integration-tests/SKILL.md create mode 100644 .github/skills/integration-tests/references/azure-auth-strategies.md create mode 100644 .github/skills/integration-tests/references/azure-servicebus-testcontainers.md create mode 100644 .github/skills/integration-tests/references/azure-storage-testcontainers.md create mode 100644 .github/skills/integration-tests/references/layer1-local-integration.md create mode 100644 .github/skills/integration-tests/references/layer2-runner-script-templates.md create mode 100644 .github/skills/integration-tests/references/layer2-smoke-tests.md create mode 100644 .github/skills/integration-tests/references/layer3-azure-integration.md create mode 100644 .github/skills/integration-tests/references/layer4-behavioral-comparison.md create mode 100644 .github/skills/rearchitect/SKILL.md create mode 100644 .github/skills/reconcile-tasks-with-plan/SKILL.md create mode 100644 .github/skills/repository-dependency-graph/SKILL.md create mode 100644 .github/skills/rulebook-create/SKILL.md create mode 100644 .github/skills/rulebook-create/charter-template.md create mode 100644 .github/skills/rulebook-create/policies-template.md create mode 100644 .github/skills/rulebook-create/references/charter-default.md create mode 100644 .github/skills/rulebook-create/references/policies-default.md create mode 100644 .github/skills/rulebook-create/references/targets-default.md create mode 100644 .github/skills/rulebook-create/targets-template.md create mode 100644 .github/skills/security-assessment-merge/SKILL.md create mode 100644 .github/skills/validate-assessment-rulebook-compliance/SKILL.md create mode 100644 .github/skills/validate-rulebook-compliance/SKILL.md create mode 100644 .github/skills/validate-rulebook-evidence/SKILL.md diff --git a/.github/modernize/ccacontext/.ccaskills b/.github/modernize/ccacontext/.ccaskills new file mode 100644 index 000000000..f1cb694f5 --- /dev/null +++ b/.github/modernize/ccacontext/.ccaskills @@ -0,0 +1,68 @@ +assessment +create-dotnet-upgrade-plan +create-java-upgrade-plan +create-modernization-plan +execute-java-upgrade-task +execute-modernization-task +execute-upgrade-task +integration-tests +reconcile-tasks-with-plan +rulebook-create +validate-assessment-rulebook-compliance +validate-rulebook-compliance +validate-rulebook-evidence +api-service-contracts +architecture-diagram +business-workflows +configuration-inventory +cve-known-vulnerabilities +cwe-code-quality +cwe-concurrency-synchronization +cwe-credentials-secrets +cwe-file-path-security +cwe-injection-attacks +cwe-memory-safety +data-architecture +dependency-map +dotnet-upgrade-assessment +fact-application-name +fact-application-port +fact-application-type +fact-architecture-pattern +fact-base-image +fact-communication-protocols +fact-compliance-requirements +fact-container-engine +fact-container-version +fact-data-classification +fact-embedded-language-usage +fact-environment-variables +fact-external-dependencies +fact-external-services +fact-hardware-requirements +fact-health-checks +fact-image-layers +fact-image-size +fact-language-dependencies +fact-licensing-information +fact-multi-stage-build +fact-network-settings +fact-operating-system +fact-orchestration-tool +fact-profile-settings +fact-resource-limits +fact-runtime-environment +fact-security-implementation +fact-service-definition +fact-servlet-container +fact-startup-instrumentation +fact-system-packages +fact-testing-framework +fact-version-information +fact-volume-mounts +fact-xml-configs +generate-report-html +generate-report-md +rearchitect +repository-dependency-graph +security-assessment-merge diff --git a/.github/modernize/ccacontext/cleanup.sh b/.github/modernize/ccacontext/cleanup.sh new file mode 100644 index 000000000..7a0ed8bef --- /dev/null +++ b/.github/modernize/ccacontext/cleanup.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# Removes CCA-copied skill directories listed in .ccaskills +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SKILLS_DIR="$(cd "$SCRIPT_DIR/../../skills" && pwd)" +SKILLS_DIR_REAL="$(cd "$SKILLS_DIR" && pwd -P)" +CCASKILLS="$SCRIPT_DIR/.ccaskills" + +if [ ! -f "$CCASKILLS" ]; then + echo "No .ccaskills file found, nothing to clean." + exit 0 +fi + +while IFS= read -r skill; do + skill="${skill%$'\r'}" + [ -z "$skill" ] && continue + + case "$skill" in + /*|*\\*|*/*|*..*) + echo "Skipping invalid skill entry: $skill" + continue + ;; + esac + + target="$SKILLS_DIR/$skill" + if [ -d "$target" ]; then + target_real="$(cd "$target" 2>/dev/null && pwd -P)" + if [ -z "$target_real" ]; then + echo "Skipping unresolved path: $skill" + continue + fi + + case "$target_real" in + "$SKILLS_DIR_REAL"/*) + rm -rf -- "$target_real" + echo "Removed: $skill" + ;; + *) + echo "Skipping out-of-scope path: $skill" + ;; + esac + fi +done < "$CCASKILLS" + +rm -f "$CCASKILLS" + +# Stage removal of .github/skills and ccacontext so the PR diff doesn't include them +git rm -rf --quiet .github/skills 2>/dev/null || true +git rm -rf --quiet .github/modernize/ccacontext 2>/dev/null || true + +echo "Cleanup complete." diff --git a/.github/skills/api-service-contracts/SKILL.md b/.github/skills/api-service-contracts/SKILL.md new file mode 100644 index 000000000..bcbbe5e28 --- /dev/null +++ b/.github/skills/api-service-contracts/SKILL.md @@ -0,0 +1,210 @@ +--- +name: api-service-contracts +description: Generate API and service communication contracts with sequence diagram +--- + +# API & Service Communication Contracts + +Analyze the project to document all services, API endpoints, communication patterns (sync/async), DTOs, and retry/circuit-breaker policies. Generate a Mermaid sequence diagram showing the primary request flow across services. Save to `.github/modernize/assessment/engines/api-service-contracts.md`. + +## Input Parameters + +- `workspace-path` (optional): Path to the project to analyze (defaults to current directory) + +## Scope Boundaries — Avoid Redundancy with Other Skills + +This skill is part of a set of four complementary assessment skills. To avoid content duplication across their output documents, observe these scope rules: + +- **Introduction**: Write a 1-2 sentence intro focused on the API surface (number of endpoints, communication style). Do NOT restate the application's technology stack, database options, or architecture type — those are covered by other skills. +- **Entity fields and persistence details** are owned by the `data-architecture` skill. In the DTOs & Contracts section, list entity/DTO **class names** and their role in the API contract (request type, response type, immutability). Do NOT reproduce full field lists, ORM annotations (cascade, fetch strategy), or table names — reference `data-architecture.md` instead. +- **Validation rules** (e.g., `@NotBlank`, custom validators) are owned by the `business-workflows` skill. Mention validation only when it affects the API contract (e.g., "returns 400 if validation fails"). Do NOT enumerate individual field constraints. +- **Caching implementation details** (provider, TTL, configuration class) are owned by the `data-architecture` skill. In the sequence diagram, you may show cache hit/miss behavior, but do NOT repeat the cache provider name, configuration details, or rationale. +- **Configuration properties and profiles** (e.g., `spring.jpa.*`, database profiles) are owned by the `configuration-inventory` skill. Do NOT list property keys/values. +- **Startup dependency chain details** (readiness probes, K8s manifests, dockerize) are owned by the `configuration-inventory` skill. Mention startup order only if it directly affects API availability. Do NOT repeat probe paths or wait mechanisms. + +## Execution Steps + +### Step 1: Generate Service Catalog Section + +Identify all independently deployable services/modules and produce the complete `## Service Catalog` section: + +- Multi-module builds: Maven modules (`pom.xml` ``), Gradle subprojects (`settings.gradle`), .NET solutions (`.sln` → `.csproj` projects), monorepo workspaces (`package.json` workspaces) +- Docker Compose services (`docker-compose.yml` service definitions) — note third-party containers vs source-built services +- Kubernetes deployments, Helm charts, or IaC definitions + +For each service extract: +- Service name and Maven module / project name +- Port number (from config files, `docker-compose.yml`, or `application.properties`/`appsettings.json`) +- Category: **API Layer** (gateways, BFFs), **Business** (domain services), **Infrastructure** (config, discovery, admin), **Observability** (tracing, metrics, dashboards) +- Purpose (one-line description) +- Key framework dependencies (from `pom.xml`, `.csproj`, `package.json`) + +### Step 2: Generate API Endpoints Inventory Section + +Scan source code for API endpoint definitions and produce the complete `## API Endpoints Inventory` section: + +- Java (Spring): `@RestController`, `@Controller`, `@GetMapping`, `@PostMapping`, `@PutMapping`, `@DeleteMapping`, `@RequestMapping` +- Java (Jakarta EE): `@Path`, `@GET`, `@POST`, `@PUT`, `@DELETE` (JAX-RS) +- .NET (ASP.NET Core): `[ApiController]`, `[HttpGet]`, `[HttpPost]`, `[HttpPut]`, `[HttpDelete]`, `[Route]` +- JavaScript/TypeScript: Express routes (`app.get`, `app.post`, `router.get`), Fastify routes, NestJS decorators (`@Get`, `@Post`) + +For each endpoint extract: +- HTTP method (GET, POST, PUT, DELETE, PATCH) +- URL path (including path parameters) +- Request type (body/query/path parameters, DTO class name) +- Response type (DTO class name, status codes) +- API versioning scheme if present (URL path, header, query parameter) +- Which service/controller it belongs to + +### Step 3: Generate Management & Observability Endpoints Section + +Identify management and observability endpoints and produce the complete `## Management & Observability Endpoints` section: + +- Spring Boot Actuator endpoints (`/actuator/health`, `/actuator/info`, `/actuator/metrics`, `/actuator/prometheus`) +- .NET health checks (`/health`, `/healthz`), Swagger UI (`/swagger`) +- Custom metrics annotations: `@Timed` (Micrometer), `[Meter]`, custom metric registrations — note the metric name and which service exposes it + +### Step 4: Generate DTOs & Contracts Section + +Analyze DTO and contract definitions and produce the complete `## DTOs & Contracts` section: + +- Find DTO / request / response model classes (records, POJOs, C# records/classes). List class names and their API role (request body, response, path/query param). Do NOT reproduce full field lists or ORM annotations — those belong in `data-architecture.md`. +- **Distinguish gateway-level DTOs** (aggregation/composition models that combine data from multiple services) from **service-level domain entities** (owned by a single service) +- Note which DTOs are immutable (Lombok `@Value`, Java records, C# records, frozen data classes) +- Identify OpenAPI/Swagger specifications (`openapi.yaml`, `swagger.json`, Springdoc/Swashbuckle annotations) +- Check for protobuf schemas (`.proto` files) or GraphQL schemas +- Note serialization configuration (Jackson, System.Text.Json, custom serializers) + +### Step 5: Generate Communication Patterns Section + +Identify inter-service and intra-service communication and produce the complete `## Communication Patterns` section: + +- **Synchronous**: REST (HttpClient, RestTemplate, WebClient, Feign), gRPC, direct method calls +- **Asynchronous**: Message queues (Kafka, RabbitMQ, Azure Service Bus, SQS), event-driven patterns, pub/sub +- **Resilience patterns**: Circuit breaker (Resilience4j, Polly, Spring Retry), retry policies, timeout configuration, bulkhead patterns — note specific timeout values and fallback behavior +- **Service discovery**: Eureka, Consul, Kubernetes DNS, Azure Service Discovery — note whether services register by logical name or hardcoded URL +- **API gateway**: Spring Cloud Gateway, Ocelot, Kong, custom gateway patterns +- **Gateway aggregation/composition**: Document how the gateway combines responses from multiple backend services (e.g., fetching owner details from one service and visit history from another, then merging them into a single response). Note the composition logic and fallback behavior when a downstream service is unavailable. +- **Client-side load balancing**: Spring Cloud LoadBalancer, Ribbon, or framework-provided balancing +- **Startup dependency chain**: Briefly note the service startup order if it affects API availability. For full details (probes, wait mechanisms, timeouts), refer to `configuration-inventory.md`. +- **Security posture**: Note whether transport security (HTTPS/TLS), authentication (JWT, OAuth2, Basic Auth, Spring Security), or authorization (RBAC, `@PreAuthorize`, role checks) are implemented at the API level. If absent, state it explicitly — e.g., "No authentication or TLS configured; all endpoints are publicly accessible with no authorization checks." Do NOT duplicate CWE security scan findings; focus only on presence or absence at the API contract level. + +### Step 6: Generate Service Technology Matrix Section + +For each service, identify which cross-cutting capabilities it uses and produce the complete `## Service Technology Matrix` section: + +- Web framework (MVC, Reactive/WebFlux, Minimal API) +- Data access (JPA, EF Core, Mongoose, etc.) +- Service discovery (client, server, or none) +- Gateway functionality +- Actuator/health checks +- Caching layer +- Metrics export (Prometheus, Application Insights, etc.) + +### Step 7: Generate Service Communication Sequence Section + +Create a **Mermaid `sequenceDiagram`** and produce the complete `## Service Communication Sequence` section: +- Show key actors: Client, API Gateway (if present), Controllers, Services, External Services, Message Brokers +- Annotate synchronous calls with solid arrows and asynchronous calls with dashed arrows +- Include request/response types where relevant +- Show error handling paths for critical flows (circuit breaker, retry) +- For gateway aggregation flows, show how multiple downstream calls are composed + +Example: + +~~~mermaid +sequenceDiagram + participant Client + participant Gateway as "API Gateway" + participant CustSvc as "Customers Service" + participant VisitSvc as "Visits Service" + participant DB as "Database" + + Client->>Gateway: GET /api/gateway/owners/1 + Gateway->>CustSvc: GET /owners/1 + CustSvc->>DB: findById(1) + DB-->>CustSvc: Owner + Pets + CustSvc-->>Gateway: OwnerDetails(pets=[Pet1,Pet2]) + Gateway->>VisitSvc: GET /pets/visits?petId=1,2 + alt Visits Service Available + VisitSvc->>DB: findByPetIdIn([1,2]) + DB-->>VisitSvc: Visits list + VisitSvc-->>Gateway: Visits(items=[...]) + else Circuit Breaker Open + Gateway-->>Gateway: Fallback - empty visits + end + Gateway->>Gateway: Merge visits into pets + Gateway-->>Client: 200 OwnerDetails + Visits +~~~ + +### Step 8: Save Output + +Save to `.github/modernize/assessment/engines/api-service-contracts.md` with this exact structure: + +``` +# API & Service Communication Contracts + +A brief introduction (1-2 sentences) summarizing the API surface and communication patterns found. + +## Service Catalog + +[Table: Service | Port | Category | Purpose] + +## API Endpoints Inventory + +[Table: Service | Method | Path | Request Type | Response Type] + +## Management & Observability Endpoints + +[Table: Service | Endpoint | Custom Metrics (if any)] + +## DTOs & Contracts + +[Description of gateway-level DTOs vs service-level entities, immutability, serialization] + +## Communication Patterns + +[Description of sync/async patterns, gateway aggregation/composition logic, circuit breaker/retry policies with timeout values, service discovery, startup dependency chain, and security posture (authentication/authorization/TLS — or explicit statement that none is configured)] + +## Service Technology Matrix + +[Table: Service | Web | Data Access | Discovery | Gateway | Actuator | Cache | Metrics] + +## Service Communication Sequence + +< Mermaid sequenceDiagram here > +``` + +## Scaling Rules + +- If the project has **more than 30 endpoints**, group by service/controller and show representative endpoints per group +- Keep the sequence diagram under **40 participants and messages** to ensure readability and GitHub rendering compatibility +- For multi-module projects, focus on inter-module communication in the sequence diagram and list all endpoints in the table +- Aggregate similar endpoints (e.g., CRUD operations on the same resource) into one table row if needed for brevity +- For the service technology matrix, use checkmarks or short labels; omit columns where no service uses the capability + +## Mermaid Syntax Rules + +- Use `sequenceDiagram` +- Avoid special characters (`@`, `#`, `$`, `%`, `&`) in participant labels — use plain text or quoted labels +- Use `->>` for synchronous calls and `-->>` for responses/async messages +- Use `participant` with alias syntax for readable labels: `participant Svc as "OrderService"` +- Use `alt`/`else`/`end` blocks to show circuit breaker fallback paths +- Do not use backticks inside node labels + +## Error Handling + +- **Unsupported project type**: Output a single line: `> ERROR: Unsupported project type. This skill supports Java, .NET, JavaScript, and TypeScript projects only.` +- **No API endpoints found**: Output: `> ERROR: No recognized API endpoints found at {workspace-path}. Verify the path is correct.` +- **Insufficient info**: Generate a best-effort document from available data. Add a note: `> Note: Some endpoints or communication patterns could not be fully identified.` + +## Success Criteria + +- Service catalog table lists all discovered services with ports, categories, and purposes +- API endpoints table lists all discovered endpoints with HTTP method, path, and types +- Management/observability endpoints are cataloged with custom metric names +- Gateway aggregation/composition patterns are documented with fallback behavior +- Service technology matrix shows per-service capabilities +- Communication patterns section describes sync/async patterns, resilience policies, and security posture (authentication, authorization, TLS — explicitly stating if none is configured) +- Mermaid sequence diagram renders correctly showing primary request flow with aggregation and fallback +- File saved to `.github/modernize/assessment/engines/api-service-contracts.md` diff --git a/.github/skills/architecture-diagram/SKILL.md b/.github/skills/architecture-diagram/SKILL.md new file mode 100644 index 000000000..e74695943 --- /dev/null +++ b/.github/skills/architecture-diagram/SKILL.md @@ -0,0 +1,190 @@ +--- +name: architecture-diagram +description: Generate architecture diagram with component relationship details from project analysis +--- + +# Architecture Diagram + +This skill generates a two-layer architecture visualization: a high-level application architecture diagram and a detailed component relationship diagram. Produce both in a single pass and save to `.github/modernize/assessment/engines/architecture-diagram.md`. + +## Input Parameters + +- `workspace-path` (optional): Path to the project to analyze (defaults to current directory) + +## Execution Steps + +### Step 1: Generate Application Architecture Section + +Analyze the project and produce the complete `## Application Architecture` section in one pass: + +**Analysis:** +- Examine build files (Java: pom.xml, build.gradle; .NET: *.csproj, *.sln; JS/TS: package.json, tsconfig.json) +- Review configuration files (application.properties, appsettings.json, .env, database/API configs) +- Scan key source files to extract: framework, major dependencies, data access patterns, external integrations, technology stack +- Identify application layers (UI, Business Logic, Data Access), data storage technologies, and external service dependencies + +**Diagram — Mermaid `flowchart TD`:** +- Application layers with technology info (use `subgraph` for grouping) +- Data storage components (specific names like "PostgreSQL", "Redis") +- External service integrations +- Data flow with descriptive arrow labels + +**Do NOT include**: individual classes/methods or migration directions. + +Example: + +~~~mermaid +flowchart TD + subgraph Client["Client Layer"] + Browser["Web Browser"] + end + subgraph App["Application Layer - Spring Boot 2.7"] + Web["Spring MVC + Thymeleaf"] + Security["Spring Security"] + Service["Business Services"] + end + subgraph Data["Data Layer"] + JPA["Spring Data JPA"] + DB[("PostgreSQL 14")] + Cache[("Redis")] + end + subgraph External["External Services"] + SMTP["SMTP Email Service"] + S3["AWS S3 Storage"] + end + + Browser -->|"HTTP requests"| Web + Web --> Security -->|"authorized"| Service + Service -->|"CRUD operations"| JPA + JPA -->|"SQL queries"| DB + Service -->|"session cache"| Cache + Service -->|"send email"| SMTP + Service -->|"file upload"| S3 +~~~ + +**Textual explanations (write immediately after the diagram):** +- **Technology Stack Summary table**: Layer | Technology | Version | Purpose (e.g., Presentation | ASP.NET MVC 5 | 5.2.7 | Server-side web framework) +- **Data Storage & External Services**: A short paragraph describing what databases, caches, message brokers, or external APIs are used and how they fit into the architecture +- **Key Architectural Decisions**: 1-3 bullet points on notable patterns (e.g., "Uses repository pattern with EF6 for data access", "Autofac provides DI with module-based registration") + +### Step 2: Generate Component Relationships Section + +Analyze component interactions and produce the complete `## Component Relationships` section in one pass: + +**Analysis:** +- Identify key component types by framework conventions: + - Java (Spring): Controllers, Services, Repositories, Configurations, Entities, DTOs, Listeners, Filters + - Java (Jakarta EE): Servlets, EJBs, CDI Beans, JPA Entities, JAX-RS Resources + - .NET (ASP.NET Core): Controllers, Services, Middleware, DbContext, Entities, Hubs, Filters + - .NET (Blazor/MVC): Pages, Components, ViewModels + - JavaScript/TypeScript (Node.js): Routes, Controllers, Services, Middleware, Models + - JavaScript/TypeScript (React/Angular/Vue): Components, Hooks, Services, Stores, Pages +- Trace dependency injection (constructor/field injection) +- Map communication patterns (REST, gRPC, message queues, events) +- Map data access patterns (service-to-repository, DbContext usage) +- Detect cross-cutting concerns (middleware, interceptors, filters) + +**Diagram — Mermaid `flowchart LR`:** +- Components grouped by architectural layer using `subgraph` (Presentation, Business Logic, Data Access, Infrastructure) +- Interaction arrows with brief labels +- Cross-cutting concerns + +**Do NOT include**: method signatures, private helpers, or external dependencies (covered by dependency-map skill). + +Example: + +~~~mermaid +flowchart LR + subgraph Presentation + UserCtrl["UserController"] + OrderCtrl["OrderController"] + end + subgraph Business["Business Logic"] + UserSvc["UserService"] + OrderSvc["OrderService"] + NotifSvc["NotificationService"] + end + subgraph DataAccess["Data Access"] + UserRepo["UserRepository"] + OrderRepo["OrderRepository"] + end + subgraph Infra["Infrastructure"] + AuthFilter["AuthenticationFilter"] + LogMiddleware["LoggingMiddleware"] + end + + UserCtrl -->|"delegates"| UserSvc + OrderCtrl -->|"delegates"| OrderSvc + OrderSvc -->|"lookups"| UserSvc + OrderSvc -->|"triggers"| NotifSvc + UserSvc -->|"queries"| UserRepo + OrderSvc -->|"queries"| OrderRepo + AuthFilter -.->|"intercepts"| UserCtrl + AuthFilter -.->|"intercepts"| OrderCtrl + LogMiddleware -.->|"wraps"| Presentation +~~~ + +**Textual explanation (write immediately after the diagram):** +- **Component Inventory table**: Component | Layer | Type | Responsibility (e.g., CatalogController | Presentation | MVC Controller | Handles catalog browsing and CRUD) + +### Step 3: Save Output + +Save the combined output to `.github/modernize/assessment/engines/architecture-diagram.md` with this exact structure: + +``` +# Architecture Diagram + +A brief introduction (1-2 sentences). + +## Application Architecture + +< Layer 1 Mermaid flowchart TD here > + +### Technology Stack Summary + +[Table: Layer | Technology | Version | Purpose] + +### Data Storage & External Services + +[Short paragraph on databases, caches, external APIs] + +### Key Architectural Decisions + +[1-3 bullet points on notable patterns] + +## Component Relationships + +< Layer 2 Mermaid flowchart LR here > + +### Component Inventory + +[Table: Component | Layer | Type | Responsibility] +``` + +## Scaling Rules + +- If the project has **more than 30 components**, aggregate by package/namespace (e.g., show `com.example.orders` as one node instead of listing every class) +- Keep each diagram under **40 nodes** to ensure readability and GitHub rendering compatibility +- For multi-module projects, focus on inter-module boundaries in Layer 1 and key components within the most important modules in Layer 2 + +## Mermaid Syntax Rules + +- Use `flowchart TD` for Layer 1 and `flowchart LR` for Layer 2 +- Avoid special characters (`@`, `#`, `$`, `%`, `&`) in node labels — use plain text +- Always quote arrow labels with double quotes: `-->|"label"|` +- Use `subgraph` for grouping, with a display name in quotes if it contains spaces +- Verify all node IDs are unique across the entire diagram + +## Error Handling + +- **Unsupported project type**: Output a single line: `> ERROR: Unsupported project type. This skill supports Java, .NET, JavaScript, and TypeScript projects only.` +- **No source code found**: Output: `> ERROR: No recognized source files found at {workspace-path}. Verify the path is correct.` +- **Insufficient info**: Generate a best-effort diagram from available data. Add a note inside the diagram: `Note["Some components could not be identified"]` + +## Success Criteria + +- Layer 1 Mermaid diagram renders correctly showing architecture with technology names, data storage, and external dependencies +- Layer 1 is accompanied by Technology Stack Summary table, Data Storage & External Services paragraph, and Key Architectural Decisions +- Layer 2 Mermaid diagram renders correctly showing component interactions grouped by architectural layer +- Layer 2 is accompanied by Component Inventory table +- File saved to `.github/modernize/assessment/engines/architecture-diagram.md` diff --git a/.github/skills/assessment/SKILL.md b/.github/skills/assessment/SKILL.md new file mode 100644 index 000000000..a9c4cae77 --- /dev/null +++ b/.github/skills/assessment/SKILL.md @@ -0,0 +1,173 @@ +--- +name: assessment +description: Run application assessment for a single repository +--- + +# Application Assessment + +This skill performs application assessment for a single repository. It supports Java, .NET, and JavaScript/TypeScript projects. + +## Input Parameters + +- `workspace-path` (optional): Path to the project to assess. Defaults to the current directory (repository root) when not specified. All assessment outputs are written relative to this path (e.g. `{workspace-path}/.github/modernize/assessment/reports/report-{reportId}/report.json`). For a repository with multiple sub-projects, pass the sub-project directory path so that each sub-project's outputs are isolated. + +## When to Use This Skill + +Use this skill when you need to: + +- Assess a Java or .NET application for cloud readiness and migration issues +- Assess a JavaScript/TypeScript project for outdated dependencies and available updates +- Generate detailed assessment reports with issue analysis and recommendations +- Understand application dependencies, frameworks, and potential migration blockers + +## What This Skill Does + +This skill performs a simplified assessment workflow: + +1. **Check Project Type and Prerequisites**: + - **For Java projects**: Verify that MCP tools are available ('appmod-precheck-assessment' and 'appmod-run-assessment') + - If these MCP tools are not configured, return immediately with setup instructions + - **For .NET projects**: Check if .NET SDK is available + - No MCP tools required for .NET assessment + - **For JavaScript/TypeScript projects**: Check if Node.js and npm are available + - No MCP tools required for JS/TS assessment + +2. **Run Assessment**: + - **For Java projects**: Trigger AppCAT analysis via Assessment MCP server + - Uses 'appmod-precheck-assessment' and 'appmod-run-assessment' MCP tools + - Auto-detects project configuration within `{workspace-path}` + - **For .NET projects**: Install and run AppCAT directly + - Install: `dotnet tool update dotnet-appcat` + - Find all .csproj files under `{workspace-path}` + - Join project paths with semicolons: `projectPaths="project1.csproj;project2.csproj"` + - Run: `dotnet-appcat analyze $projectPaths --source Solution --target Any --serializer APPMODJSON --code --privacyMode Restricted --non-interactive --report {workspace-path}\.github\modernize\appcat\result\report.json` + - **For JavaScript/TypeScript projects**: Install and run npm-check-updates + - Install: `npm install -g npm-check-updates@19.6.3 --prefix {tool-install-dir}` + - Run: `ncu --format group --packageFile {workspace-path}/package.json` + - Save the output to `{workspace-path}/.github/modernize/assessment/js-assessment-report.md` + - Analyzes code for cloud migration issues or dependency updates + - Generates structured assessment data + +3. **Save Report to Versioned Directory (Java and .NET only)**: + - **For Java projects**: Search for `report.json` under `{workspace-path}/.github/modernize/appcat/result/` or `{workspace-path}/.github/appmod/appcat/result/` subdirectories + - **For .NET projects**: The report is at `{workspace-path}/.github/modernize/appcat/result/report.json` + - Read the generated `report.json` and extract the `metadata.analysisStartTime` field + - Format the timestamp as `yyyyMMddHHmmss` to produce the `reportId` (e.g. `2024-06-15T14:30:52Z` becomes `20240615143052`) + - Create the versioned directory: `mkdir -p {workspace-path}/.github/modernize/assessment/reports/report-{reportId}` + - Move the report to: `{workspace-path}/.github/modernize/assessment/reports/report-{reportId}/report.json` + - This versioned report should be included in the pull request + +## How to Use + +### Prerequisites + +**For Java projects**: +- MCP tools must be available: 'appmod-precheck-assessment' and 'appmod-run-assessment' +- If tools are not configured, the skill will return instructions for setup + +**For .NET projects**: +- .NET SDK must be installed +- No MCP tools required - appcat will be installed and run directly via .NET CLI +- The assessment will automatically install `dotnet-appcat` tool if not already present + +**For JavaScript/TypeScript projects**: +- Node.js and npm must be installed +- No MCP tools required - npm-check-updates will be installed and run directly via npm +- The assessment will automatically install `npm-check-updates` if not already present + +### Triggering Assessment + +Simply express the intent to assess the application. Example prompts: + +- "Assess the application" +- "Run assessment for this project" + +The assessment process automatically: +- Detects project language and framework within `{workspace-path}` +- **For Java**: Uses MCP tools to install and run AppCAT +- **For .NET**: Installs dotnet-appcat tool and runs analysis directly +- **For JavaScript/TypeScript**: Installs npm-check-updates and runs dependency analysis +- Executes comprehensive analysis +- **For Java**: Generates report at `{workspace-path}/.github/modernize/appcat/result/` or `{workspace-path}/.github/appmod/appcat/result/report.json` +- **For .NET**: Generates report at `{workspace-path}/.github/modernize/appcat/result/report.json` +- **For JavaScript/TypeScript**: Generates report at `{workspace-path}/.github/modernize/assessment/js-assessment-report.md` + +### Report Saving + +**For Java projects**: +1. Search for `report.json` files under `{workspace-path}/.github/modernize/appcat/result/` or `{workspace-path}/.github/appmod/appcat/result/` subdirectories +2. If multiple reports exist, identify the most recently modified one +3. Read the report and extract `metadata.analysisStartTime`, format as `yyyyMMddHHmmss` to get `reportId` +4. Move the report to `{workspace-path}/.github/modernize/assessment/reports/report-{reportId}/report.json` +5. Include this versioned report in the pull request + +**For .NET projects**: +1. Report is initially generated at `{workspace-path}/.github/modernize/appcat/result/report.json` +2. Read the report and extract `metadata.analysisStartTime`, format as `yyyyMMddHHmmss` to get `reportId` +3. Move the report to `{workspace-path}/.github/modernize/assessment/reports/report-{reportId}/report.json` +4. Include this versioned report in the pull request + +**For JavaScript/TypeScript projects**: +1. Report is directly generated at `{workspace-path}/.github/modernize/assessment/js-assessment-report.md` +2. Include this report in the pull request + +## Report Output Location + +Report location depends on project type: + +**For Java projects** (via MCP server): +- Initially stored under `{workspace-path}/.github/modernize/appcat/result/` or `{workspace-path}/.github/appmod/appcat/result/` subdirectories +- Saved to versioned directory: `{workspace-path}/.github/modernize/assessment/reports/report-{reportId}/report.json` + +Where `{reportId}` is derived from `metadata.analysisStartTime` in the report, formatted as `yyyyMMddHHmmss`. + +**For .NET projects** (direct execution): +- Initially generated at: `{workspace-path}/.github/modernize/appcat/result/report.json` +- Moved to versioned directory: `{workspace-path}/.github/modernize/assessment/reports/report-{reportId}/report.json` + +**For JavaScript/TypeScript projects** (direct execution): +- Directly generated at: `{workspace-path}/.github/modernize/assessment/js-assessment-report.md` + +## Success Criteria + +Assessment is complete when: +- ✅ **For Java**: MCP server is available (or clear instructions provided if not) +- ✅ **For .NET**: .NET SDK is available and dotnet-appcat tool is installed +- ✅ **For JavaScript/TypeScript**: Node.js and npm are available and npm-check-updates is installed +- ✅ AppCAT analysis executes without errors (Java/.NET) or ncu analysis executes without errors (JS/TS) +- ✅ **For Java and .NET**: Report generated at `{workspace-path}/.github/modernize/assessment/reports/report-{reportId}/report.json` +- ✅ **For JavaScript/TypeScript**: Report generated at `{workspace-path}/.github/modernize/assessment/js-assessment-report.md` +- ✅ Report metadata includes assessment tool version, timestamp, and configuration + +## Troubleshooting + +**Prerequisites Not Met**: +- **For Java**: Verify MCP tools are available ('appmod-precheck-assessment' and 'appmod-run-assessment') + - Return immediately with setup instructions if tools are not available + - Do not attempt to run assessment without MCP +- **For .NET**: Verify .NET SDK is installed + - Check with `dotnet --version` command + - Provide installation instructions if .NET SDK is missing +- **For JavaScript/TypeScript**: Verify Node.js and npm are installed + - Check with `npm --version` command + - Provide installation instructions if npm is missing + +**Assessment Failures**: +- Unsupported project type (only Java, .NET, and JavaScript/TypeScript supported) +- **For Java**: MCP server communication errors +- **For .NET**: + - dotnet-appcat tool installation failure + - dotnet-appcat command execution errors +- **For JavaScript/TypeScript**: + - npm-check-updates installation failure + - ncu command execution errors + - No package.json found at `{workspace-path}/package.json` +- Invalid project structure or build configuration + +**Report Generation Issues**: +- **For Java**: No report.json found under `{workspace-path}/.github/modernize/appcat/result/` or `{workspace-path}/.github/appmod/appcat/result/` subdirectories after MCP execution +- **For .NET**: Report not generated at `{workspace-path}/.github/modernize/appcat/result/report.json`, or `metadata.analysisStartTime` missing from report +- **For JavaScript/TypeScript**: Report not generated at `{workspace-path}/.github/modernize/assessment/js-assessment-report.md` +- Report file is corrupted or invalid JSON (Java/.NET only) + +For any failure, provide clear error messages and troubleshooting steps. diff --git a/.github/skills/business-workflows/SKILL.md b/.github/skills/business-workflows/SKILL.md new file mode 100644 index 000000000..73d79be9b --- /dev/null +++ b/.github/skills/business-workflows/SKILL.md @@ -0,0 +1,199 @@ +--- +name: business-workflows +description: Generate core business workflow documentation with sequence diagram +--- + +# Core Business Workflows + +Analyze the project to document business processes end-to-end, domain entities, business rules, service-to-domain mapping, cross-service data flows, and decision logic. Generate a Mermaid sequence diagram showing the primary business workflow. Save to `.github/modernize/assessment/engines/business-workflows.md`. + +## Input Parameters + +- `workspace-path` (optional): Path to the project to analyze (defaults to current directory) + +## Scope Boundaries — Avoid Redundancy with Other Skills + +This skill is part of a set of four complementary assessment skills. To avoid content duplication across their output documents, observe these scope rules: + +- **Introduction**: Write a 1-2 sentence intro focused on the business domain (what the application does for its users). Do NOT restate the technology stack, database options, or framework versions. +- **Domain Entities table**: Focus on business meaning — entity description, bounded context, and business relationships. Do NOT reproduce entity field lists, data types, PK/FK annotations, or ORM mapping details (cascade, fetch strategy) — those are owned by the `data-architecture` skill. +- **Validation rules in workflow steps**: When describing a workflow step that involves validation, reference the rule by name (e.g., "PetValidator checks name and birthDate") rather than re-listing every constraint. Enumerate the full validation rules only once in the "Business Rules & Decision Logic" section. +- **Caching behavior**: If caching affects a workflow (e.g., vet list served from cache), mention the business impact (e.g., "vet data served from cache, reducing load") but do NOT describe the cache provider, TTL, configuration class, or JMX statistics — those are owned by the `data-architecture` skill. +- **API endpoint paths and HTTP methods**: Only mention endpoint paths as entry points for workflows (e.g., "Staff submits POST /owners/new"). Do NOT create endpoint inventory tables — those are owned by the `api-service-contracts` skill. + +## Execution Steps + +### Step 1: Generate Domain Entities Section + +Identify the domain model and produce the complete `## Domain Entities` section: + +- Identify domain entities and aggregates (DDD patterns if present) +- Focus on business meaning — entity description, bounded context, and business relationships +- Do NOT reproduce entity field lists, data types, PK/FK annotations, or ORM mapping details (cascade, fetch strategy) — those are owned by the `data-architecture` skill + +### Step 2: Generate Service-to-Domain Mapping Section + +Map each service to its bounded context and owned entities, then produce the complete `## Service-to-Domain Mapping` section (applies to microservice or multi-module architectures): + +- Service name → bounded context (e.g., `customers-service` → Customer Management, `visits-service` → Appointment Management) +- Domain entities owned by each service/context +- Cross-context data exchange patterns: how domains communicate (REST API, events, shared database) +- Data that spans contexts (e.g., `petId` as a foreign key in visits-service referencing customers-service's Pet entity) +- Aggregation boundaries: which service is the source of truth for which data + +### Step 3: Generate Primary Workflows Section + +Scan for business process entry points, trace each significant workflow end-to-end, and produce the complete `## Primary Workflows` section: + +**Entry points to scan:** +- Controllers/endpoints that initiate business processes (not just CRUD — look for multi-step operations) +- **API Gateway aggregation endpoints** that compose responses from multiple backend services — these are business workflow entry points even though they live in the gateway layer (e.g., fetching owner details combined with visit history) +- Scheduled tasks (`@Scheduled`, Quartz, Hangfire, cron jobs, `BackgroundService`) +- Event listeners (`@EventListener`, `@KafkaListener`, `INotificationHandler`, message handlers) +- CLI commands or batch job entry points +- Startup/initialization routines that set up business state + +**For each significant entry point, trace the flow:** +- Entry point → service layer → domain logic → persistence +- The sequence of operations: validation → business rule check → state mutation → side effects +- Branching logic (if/else, switch, strategy pattern) that represents business decisions +- Orchestration vs choreography patterns in multi-service workflows + +### Step 4: Generate Cross-Service Data Flows Section + +Trace cross-service data composition flows end-to-end and produce the complete `## Cross-Service Data Flows` section: + +- Gateway aggregation patterns: e.g., gateway fetches owner from customers-service → extracts pet IDs → fetches visits from visits-service → merges visits into pet records → returns composite response +- Which service provides which data and how they are joined/merged +- Circuit breaker fallback behavior that affects business outcomes (e.g., "when visits-service is unavailable, owner details are returned without visit history" — this is a business-relevant degradation, not just a technical detail) + +### Step 5: Generate Business Workflow Sequence Section + +Create a **Mermaid `sequenceDiagram`** showing the primary business workflow end-to-end and produce the complete `## Business Workflow Sequence` section: + +- Show the most important business process (e.g., "customer places order", "owner registers pet and schedules visit", "gateway aggregates owner with visit history") +- Include actors, services, and domain entities as participants +- Show business rule checks and decision points +- Annotate with business-relevant labels (not technical method names) +- Use `alt`/`else` blocks to show circuit breaker fallback paths that affect business outcomes +- Show cross-service data aggregation flows + +Example: + +~~~mermaid +sequenceDiagram + participant Owner + participant Gateway as "API Gateway" + participant CustSvc as "Customer Service" + participant VisitSvc as "Visit Service" + participant DB as "Database" + + Owner->>Gateway: View my pets and visits + Gateway->>CustSvc: Get owner details + CustSvc->>DB: Find owner with pets + DB-->>CustSvc: Owner + Pet list + CustSvc-->>Gateway: OwnerDetails(pets) + + Gateway->>Gateway: Extract pet IDs from response + Gateway->>VisitSvc: Get visits for pets (batch) + alt Visit Service Available + VisitSvc->>DB: Find visits by pet IDs + DB-->>VisitSvc: Visit records + VisitSvc-->>Gateway: Visits per pet + Gateway->>Gateway: Merge visits into pet records + else Visit Service Unavailable (Circuit Breaker) + Note over Gateway: Fallback - return owner without visits + end + Gateway-->>Owner: Complete owner profile with visits +~~~ + +### Step 6: Generate Business Rules & Decision Logic Section + +Extract and document business rules and cross-cutting concerns, and produce the complete `## Business Rules & Decision Logic` section: + +**Business Rules:** +- **Validation rules**: Input validation, field constraints, format checks, custom validators +- **Decision logic**: Conditional business logic, pricing rules, eligibility checks, approval workflows +- **State transitions**: Entity lifecycle states (e.g., Order: Created → Confirmed → Shipped → Delivered), state machines +- **Business constraints**: Uniqueness rules, capacity limits, temporal constraints (booking windows, cooldown periods) +- **Computed values**: Derived fields, calculated totals, aggregated metrics +- **Data integrity rules**: Bidirectional relationship maintenance (e.g., `owner.addPet(pet)` ensuring both sides of the relationship are set) + +**Cross-Cutting Concerns:** +- **Transactions**: Transaction boundaries, `@Transactional` scope, saga patterns, eventual consistency +- **Error handling**: Business exception types, compensating actions, dead-letter handling +- **Audit/logging**: Business event logging, audit trails, change tracking +- **Authorization**: Business-level authorization rules (role-based, attribute-based, resource ownership) + +### Step 7: Save Output + +Save to `.github/modernize/assessment/engines/business-workflows.md` with this exact structure: + +``` +# Core Business Workflows + +A brief introduction (1-2 sentences) summarizing the application's business domain. + +## Domain Entities + +[Table: Entity | Service / Bounded Context | Description | Key Relationships] + +## Service-to-Domain Mapping + +[Table: Service | Domain Context | Owned Entities | External Dependencies] + +## Primary Workflows + +### Workflow 1: [Name] + +[Description, steps, business rules involved, cross-service interactions] + +### Workflow 2: [Name] + +[Description, steps, business rules involved] + +## Cross-Service Data Flows + +[Description of aggregation/composition patterns, which service provides which data, how data is joined, fallback behavior when services are unavailable] + +## Business Workflow Sequence + +< Mermaid sequenceDiagram here, with alt/else blocks for fallback paths > + +## Business Rules & Decision Logic + +[Summary of key business rules, validation rules, state transitions, and decision points] +``` + +## Scaling Rules + +- If the project has **more than 10 distinct workflows**, focus on the 3-5 most important business processes and summarize the rest in a "Other Workflows" section +- Keep the sequence diagram under **40 participants and messages** to ensure readability and GitHub rendering compatibility +- For multi-module projects, focus on the primary end-to-end business workflow that spans modules +- Aggregate minor CRUD operations and show only workflows that involve business logic beyond simple create/read/update/delete + +## Mermaid Syntax Rules + +- Use `sequenceDiagram` +- Avoid special characters (`@`, `#`, `$`, `%`, `&`) in participant labels — use plain text or quoted labels +- Use `->>` for synchronous calls and `-->>` for responses +- Use `participant` with alias syntax for readable labels: `participant Svc as "OrderService"` +- Use `Note over` for annotations about business decisions or fallback behavior +- Use `alt`/`else`/`end` blocks for decision points and circuit breaker fallbacks +- Do not use backticks inside participant labels + +## Error Handling + +- **Unsupported project type**: Output a single line: `> ERROR: Unsupported project type. This skill supports Java, .NET, JavaScript, and TypeScript projects only.` +- **No business logic found**: Output: `> ERROR: No recognized business logic or workflows found at {workspace-path}. The project may be a library or framework without business processes.` +- **Insufficient info**: Generate a best-effort document from available data. Add a note: `> Note: Some workflows or business rules could not be fully traced.` + +## Success Criteria + +- Domain entities table lists key entities with their owning service/bounded context, descriptions, and relationships +- Service-to-domain mapping table maps each service to its domain context and owned entities +- At least one primary workflow is documented with steps, business rules, and cross-service interactions +- Cross-service data flows describe aggregation/composition patterns with fallback behavior +- Mermaid sequence diagram renders correctly showing end-to-end business workflow with `alt`/`else` blocks for fallbacks +- Business rules section summarizes validation, decision logic, state transitions, and constraints +- File saved to `.github/modernize/assessment/engines/business-workflows.md` diff --git a/.github/skills/configuration-inventory/SKILL.md b/.github/skills/configuration-inventory/SKILL.md new file mode 100644 index 000000000..780125774 --- /dev/null +++ b/.github/skills/configuration-inventory/SKILL.md @@ -0,0 +1,205 @@ +--- +name: configuration-inventory +description: Generate comprehensive configuration and externalized settings inventory +--- + +# Configuration & Externalized Settings Inventory + +Analyze the project to produce a comprehensive inventory of all configuration sources, build profiles, runtime profiles, externalized properties, secrets workflows, feature flags, startup dependencies, and framework versions. Save to `.github/modernize/assessment/engines/configuration-inventory.md`. + +> Note: This skill produces a comprehensive reference document. For structured findings suitable for automated processing, see `fact-profile-settings`, `fact-environment-variables`, and `fact-xml-configs`. + +## Input Parameters + +- `workspace-path` (optional): Path to the project to analyze (defaults to current directory) + +## Scope Boundaries — Avoid Redundancy with Other Skills + +This skill is part of a set of four complementary assessment skills. To avoid content duplication across their output documents, observe these scope rules: + +- **Introduction**: Write a 1-2 sentence intro focused on the configuration landscape (number of config sources, profiles, secrets approach). Do NOT restate the application's architecture type, business domain, or API surface. +- **Database architecture details** (entity models, ER diagrams, ORM mappings, caching strategy rationale, repository methods) are owned by the `data-architecture` skill. In the Properties Inventory, list database-related property keys and values as raw configuration entries, but do NOT explain their behavioral implications (e.g., do not explain what `spring.jpa.open-in-view=false` means for lazy loading — that belongs in `data-architecture.md`). +- **API endpoints** are owned by the `api-service-contracts` skill. Do NOT list HTTP endpoints, controller routes, or actuator paths. +- **Business workflows and validation rules** are owned by the `business-workflows` skill. Do NOT describe business processes or entity validation constraints. +- **Entity/domain model listings** are owned by `data-architecture` and `business-workflows`. Do NOT enumerate entity names, fields, or relationships. + +## Execution Steps + +### Step 1: Generate Configuration Sources Section + +Identify all configuration files and sources and produce the complete `## Configuration Sources` section: + +- Java (Spring): `application.properties`, `application.yml`, `bootstrap.properties`, `bootstrap.yml` — note that `bootstrap.*` files are distinct from `application.*` (bootstrap configures the config server connection and runs before application context; application configures the app itself) +- .NET: `appsettings.json`, `appsettings.{Environment}.json`, `web.config`, `launchSettings.json` +- JavaScript/TypeScript: `.env`, `.env.local`, `.env.production`, `config/*.js`, `config/*.ts` +- Shared: `docker-compose.yml` environment sections, Kubernetes ConfigMaps/Secrets YAML files +- Config server references: Spring Cloud Config (note the external Git repository URI), Azure App Configuration, AWS AppConfig, Consul KV +- Secret stores: HashiCorp Vault, Azure KeyVault, AWS Secrets Manager references +- External configuration repositories: document the URI/path of any external config repos (e.g., `spring.cloud.config.server.git.uri`) + +### Step 2: Generate Build Profiles Section + +Identify build-time profiles that affect compilation, packaging, and dependency resolution, and produce the complete `## Build Profiles` section: + +- **Java/Maven**: profiles in `pom.xml` (e.g., `springboot`, `buildDocker`, `dev`, `cloud`) — for each, document activation condition (auto, manual `-P`, system property `-Denv=`), purpose, and key dependencies or plugins added +- **Java/Gradle**: build types and flavors in `build.gradle` +- **.NET**: build configurations (Debug, Release), conditional compilation symbols, MSBuild properties +- **JavaScript/TypeScript**: build scripts in `package.json`, webpack/vite/esbuild configurations per environment + +For each build profile extract: +- Profile name +- Activation condition (automatic, manual flag, system property, environment variable) +- Purpose (what it enables) +- Key dependencies or plugins added/removed + +### Step 3: Generate Runtime Profiles Section + +List all runtime profile-specific or environment-specific configuration and produce the complete `## Runtime Profiles` section: + +- Java (Spring): Profile-specific files (`application-dev.yml`, `application-prod.yml`), `@Profile` annotations, `spring.profiles.active` settings, combined profile activation (e.g., `mysql,key-vault`) +- .NET: Environment-specific files (`appsettings.Development.json`, `appsettings.Production.json`), `ASPNETCORE_ENVIRONMENT` usage +- JavaScript/TypeScript: `.env.development`, `.env.production`, NODE_ENV-based branching +- Identify profile activation conditions, defaults, and how profiles compose (multiple active profiles) + +### Step 4: Generate Properties Inventory Section + +For each service/module, catalog all configuration properties and produce the complete `## Properties Inventory` section: + +- Property keys with their default values +- Which profiles/environments override each property +- Data types and expected value ranges (where inferable) +- Properties sourced from environment variables (`${ENV_VAR}`, `%ENV_VAR%`) +- Placeholder references and property resolution chain + +> Do NOT include JVM startup parameters, `-Xms`/`-Xmx` heap settings, `-D` system properties, container memory/CPU limits, or instance counts here — those belong in the `## Startup Parameters & Resource Requirements` section (Step 5). + +### Step 5: Generate Startup Parameters & Resource Requirements Section + +Document JVM startup options, runtime parameters, and per-service resource allocations, and produce the complete `## Startup Parameters & Resource Requirements` section: + +- JVM heap settings (`-Xms`, `-Xmx`) per service +- System properties passed at startup (`-Dspring.profiles.active=`, `-Dazure.keyvault.uri=`, etc.) +- Docker/container environment variable overrides (`SPRING_PROFILES_ACTIVE`, `ASPNETCORE_ENVIRONMENT`) +- Memory allocation per service (Docker `mem_limit`, Kubernetes `resources.requests/limits`, cloud deployment settings) +- CPU allocation if specified +- Instance count and scaling configuration +- JVM heap settings mapped to service memory allocation (e.g., `-Xms2048m -Xmx2048m` for 2Gi services) + +### Step 6: Generate Startup Dependency Chain Section + +Map the service startup order and readiness dependencies and produce the complete `## Startup Dependency Chain` section: + +- Which services must start before others (e.g., config-server → discovery-server → business services → gateway) +- Health-check/wait mechanisms: `dockerize` wait-for-TCP, Kubernetes readiness probes, Spring Cloud Config retry, Docker Compose `depends_on` with health checks +- Startup timeout configurations +- Service readiness indicators (actuator health endpoints, custom health checks) + +### Step 7: Generate Secrets & Sensitive Configuration Section + +Flag sensitive configuration entries and document the secrets provisioning workflow, and produce the complete `## Secrets & Sensitive Configuration` section (including the `### Secrets Provisioning Workflow` subsection): + +- Database passwords, API keys, connection strings with credentials +- Secret references: KeyVault URIs, Vault paths, encrypted property values +- Entries marked as sensitive by framework conventions (e.g., `spring.datasource.password`) +- **Do NOT output actual secret values** — show the reference path or "[MASKED]" placeholder +- Note encryption methods if present (Jasypt, DPAPI, sealed secrets) + +Document how secrets flow through the system (`### Secrets Provisioning Workflow`): +- Secret source: environment variables, Key Vault, Vault, AWS Secrets Manager, sealed secrets +- Identity/access model: managed identities, service principals, RBAC permissions (e.g., "system-assigned managed identity with `get` and `list` permissions on Key Vault") +- Provisioning sequence: how secrets are set up during deployment (e.g., GitHub Actions retrieves service principal credentials → authenticates → creates MySQL secrets → binds to services) +- Which services need which secrets (e.g., data services need MySQL connection strings, all services need config server credentials) + +### Step 8: Generate Feature Flags Section + +Identify feature toggles and conditional configuration and produce the complete `## Feature Flags` section: + +- Feature flag frameworks: Spring Feature Flags, LaunchDarkly, Unleash, .NET FeatureManagement, custom toggles +- Conditional beans/services (`@ConditionalOnProperty`, `@ConditionalOnExpression`) +- A/B testing flags and gradual rollout configurations +- Default values and controlling sources (config file, environment variable, remote service) + +### Step 9: Generate Framework & Runtime Versions Section + +Catalog the technology stack versions that affect configuration and produce the complete `## Framework & Runtime Versions` section: + +- Core framework versions: Spring Boot, Spring Cloud, ASP.NET Core, Node.js, Express +- Target language/runtime version: Java 8/11/17/21, .NET 6/7/8, Node.js 18/20 +- Key library versions: Hibernate, EF Core, Resilience4j, Eureka, etc. +- Docker base images and their versions (e.g., `openjdk:11-jre`, `mcr.microsoft.com/dotnet/aspnet:8.0`) +- Build tool versions: Maven, Gradle, MSBuild, npm/yarn/pnpm + +### Step 10: Save Output + +Save to `.github/modernize/assessment/engines/configuration-inventory.md` with this exact structure: + +``` +# Configuration & Externalized Settings Inventory + +A brief introduction (1-2 sentences) summarizing the configuration landscape. + +## Configuration Sources + +[Table: Source | Type | Path/Location | Notes] + +## Build Profiles + +[Table: Profile | Activation | Purpose | Key Dependencies/Plugins] + +## Runtime Profiles + +[Table: Profile | Activation Method | Config Files | Key Overrides] + +## Properties Inventory + +[Per-service tables: Property Key | Default | Profiles | Source] + +## Startup Parameters & Resource Requirements + +[Table: Service | JVM/Runtime Options | Memory | Instance Count] + +## Startup Dependency Chain + +[Ordered list: Service → waits for → Service, with mechanism (dockerize, health check, etc.)] + +## Secrets & Sensitive Configuration + +[Table: Secret Reference | Type | Storage (masked)] + +### Secrets Provisioning Workflow + +[Description of how secrets flow: source → identity/access → binding → services] + +## Feature Flags + +[Table: Flag Name | Default | Controlled By] + +## Framework & Runtime Versions + +[Table: Component | Version | Source] +``` + +## Scaling Rules + +- If the project has **more than 100 properties**, group by category (database, messaging, security, etc.) and show representative examples with counts +- For multi-module projects, organize the properties inventory by module/service +- Collapse repetitive property patterns (e.g., 20 similar cache TTL settings) into a summary row with count + +## Error Handling + +- **Unsupported project type**: Output a single line: `> ERROR: Unsupported project type. This skill supports Java, .NET, JavaScript, and TypeScript projects only.` +- **No configuration files found**: Output: `> ERROR: No recognized configuration files found at {workspace-path}. Verify the path is correct.` +- **Insufficient info**: Generate a best-effort inventory from available data. Add a note: `> Note: Some configuration sources or properties could not be fully identified.` + +## Success Criteria + +- Configuration sources table lists all discovered config files, external config repos, and secret stores +- Build profiles are documented separately from runtime profiles with activation conditions and purposes +- Runtime profiles are documented with config files and key overrides +- Properties inventory covers all discovered properties with defaults and sources +- Startup parameters and resource requirements are documented per service +- Startup dependency chain shows service boot order with wait mechanisms +- Secrets are identified with references (no actual values exposed) and the provisioning workflow is described +- Feature flags are cataloged with defaults and controlling sources +- Framework and runtime versions are documented +- File saved to `.github/modernize/assessment/engines/configuration-inventory.md` diff --git a/.github/skills/create-dotnet-upgrade-plan/SKILL.md b/.github/skills/create-dotnet-upgrade-plan/SKILL.md new file mode 100644 index 000000000..bb6797d8e --- /dev/null +++ b/.github/skills/create-dotnet-upgrade-plan/SKILL.md @@ -0,0 +1,143 @@ +--- +name: create-dotnet-upgrade-plan +description: Creates a .NET upgrade plan (plan.md and tasks.json) for a repository. Analyzes the project to determine if a .NET version upgrade is needed and generates a structured upgrade task. Use when the user wants to create, generate, or prepare an upgrade/migration plan for a .NET project or solution. +--- + +# Create .NET Upgrade Plan + +Generate an upgrade plan (`plan.md` and `.metadata/tasks.json`) for a .NET project or solution. This skill **only creates the plan** — it does not execute the upgrade. The upgrade execution is handled by the `modernize-dotnet-upgrade-engineer` agent during plan execution. + +> **Supported**: .NET upgrades only. Other languages are not supported. + +--- + +## CRITICAL: HEADLESS EXECUTION MODE + +**This skill operates in fully autonomous headless mode. NO USER INTERACTION IS PERMITTED.** + +### Non-Negotiable Rules + +1. **NEVER stop, pause, or wait for user input** — not for confirmations, selections, reviews, or approvals +2. **NEVER ask "Would you like to proceed?"** — just proceed +3. **NEVER ask "Should I continue?"** — just continue +4. **ALWAYS complete the entire workflow** — from start to finish without interruption + +--- + +## Input Parameters + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `upgrade-prompt` | Yes | The user's upgrade request (e.g., ".NET 10", "net10.0", "latest LTS") | +| `modernization-work-folder` | Yes | The folder to save the upgrade plan outputs | + +--- + +## Input Validation + +**Valid requests**: .NET version upgrades, LTS migrations, .NET Framework to modern .NET migrations + +**Invalid requests**: Feature additions, bug fixes, refactoring, containerization, deployment, non-.NET languages + +If invalid, output exactly and STOP: +``` +ERROR: The provided prompt is not a valid upgrade request. Please specify a target version (e.g., '.NET 10', 'net10.0'). +``` + +If unsupported language, output exactly and STOP: +``` +ERROR: Only .NET upgrades are supported. The requested language is not supported. +``` + +--- + +## Guidelines + +Refer to `dotnet-upgrade-guideline.md` for the rules on when to create an upgrade task, target version selection, and framework compatibility. + +Key rules from the guideline: +- Only add an upgrade task if: the project is EOL, has Azure SDK incompatibility, or the user explicitly requests it +- Always upgrade to the **latest LTS** version unless the user specifies a different target +- Current latest LTS: **.NET 10** (`net10.0`) +- Create a **single** upgrade task that encompasses all necessary changes + +--- + +## Workflow + +### Step 1: Analyze the Project + +Examine the project to determine: +1. **Current .NET version**: Read `.csproj` files to find `` or `` +2. **Project type**: Is it .NET Framework (requires SDK-style conversion), .NET Core, or modern .NET? +3. **Solution structure**: Is it a single project or a multi-project solution? List all projects. + +### Step 2: Determine Upgrade Need + +Apply the rules from `dotnet-upgrade-guideline.md`: +- If the project's .NET version is EOL → upgrade needed +- If the project targets .NET Framework < 4.6.2 (Azure SDK incompatibility) → upgrade needed +- If the user explicitly requests an upgrade → upgrade needed +- Otherwise → no upgrade needed, output: `ERROR: No upgrade is needed. The project is already on a supported .NET version.` + +### Step 3: Generate plan.md + +Create `plan.md` in `${modernization-work-folder}/` with: +- Source .NET version (detected from the project) +- Target .NET version (from user request or latest LTS) +- List of projects in the solution +- High-level description of what the upgrade entails (e.g., SDK-style conversion, TFM update, NuGet updates, API migration) + +### Step 4: Generate tasks.json + +Generate `tasks.json` in `${modernization-work-folder}/.metadata/` following `tasks-schema.json` and `upgrade-plan-template.md`. + +Create a **single** upgrade task: + +**Task Generation Rules:** + +| Rule | Requirement | +|------|-------------| +| Task type | Use `upgrade` type | +| Task count | Exactly **one** upgrade task | +| Description | Include source and target .NET versions, project names | +| Reason | Include a **non-empty** `reason` field explaining why the upgrade is needed (for example: current .NET version is outdated or unsupported, target version standardization, required compatibility/security/support improvements) | +| Requirements | High-level summary of the upgrade scope (source version, target version, general areas affected). Do not include implementation details — the upgrade agent determines the specific steps. | +| Skills | Empty array `[]` — the upgrade agent handles execution internally | +| successCriteria values | Must be **strings** (`"true"`, not `true`) | +| status | Set to `"pending"` | +| id format | Use pattern `001-upgrade-dotnet-to-{target}` (e.g., `001-upgrade-dotnet-to-net10`) | + +--- + +## Success Criteria + +All of the following must be true: + +- [ ] `plan.md` exists in `${modernization-work-folder}/` +- [ ] `tasks.json` exists in `${modernization-work-folder}/.metadata/` +- [ ] `plan.md` clearly states source and target .NET versions +- [ ] `tasks.json` follows schema with all required fields +- [ ] Exactly one upgrade task in tasks.json +- [ ] Workflow completed without user interaction + +--- + +## Error Handling + +| Problem | Solution | +|---------|----------| +| No .csproj found | Output `ERROR: No supported .NET project files (.csproj) found in the repository.` | +| Cannot determine current .NET version | Output `ERROR: Could not determine the current .NET version from project files.` | +| Project already on target version | Output `ERROR: The project is already on the target .NET version.` | + +--- + +## Anti-Patterns (NEVER DO THESE) + +| Don't | Do Instead | +|-------|------------| +| Stop to ask user for confirmation | Accept defaults and continue | +| Create multiple granular upgrade tasks | Create a single upgrade task | +| Wait for user review | Complete workflow, then show final results | +| Ask "Should I proceed?" | Just proceed | diff --git a/.github/skills/create-dotnet-upgrade-plan/dotnet-upgrade-guideline.md b/.github/skills/create-dotnet-upgrade-plan/dotnet-upgrade-guideline.md new file mode 100644 index 000000000..a5c97e03a --- /dev/null +++ b/.github/skills/create-dotnet-upgrade-plan/dotnet-upgrade-guideline.md @@ -0,0 +1,43 @@ +# .NET Upgrade Task Guidelines + +Only add a .NET upgrade task if one of the following conditions is met: + +1. **End of Life (EOL)**: The project's .NET version is out of mainstream support. +2. **Azure SDK Compatibility**: The project targets a .NET Framework version older than 4.6.2, which does not support `netstandard2.0` and cannot use modern Azure SDK (`Azure.*`) packages. +3. **User Request**: The user explicitly requests a .NET version upgrade. + +The upgrade task must be the first task if it exists. + +### Target Version + +Always upgrade to the **latest LTS** version unless the user explicitly specifies a different target version. + +- Current latest LTS: **.NET 10** (`net10.0`) + +### Azure SDK Minimum .NET Version + +The modern Azure SDK for .NET (`Azure.*` packages) targets `netstandard2.0` as its baseline. The following .NET Framework versions do **not** support `netstandard2.0` and require an upgrade: + +| .NET Framework Version | `netstandard2.0` Support | Action | +|------------------------|:------------------------:|--------| +| 4.5 and below | ❌ | Upgrade required | +| 4.6 | ❌ | Upgrade required | +| 4.6.1 | ⚠️ Unreliable | Upgrade recommended | +| 4.6.2+ | ✅ | No upgrade needed for Azure SDK compatibility | + +> **Note**: .NET Framework 4.6.1 is technically listed as supporting `netstandard2.0` but has known issues. Microsoft recommends 4.7.2+ for reliable support. The Azure SDK explicitly targets `net462` as its minimum. + +## Framework Compatibility + +| Source Framework | Target Framework | SDK-Style Conversion Required | +|-----------------|:----------------:|:----------------------------:| +| .NET Framework 4.x | net10.0 | Yes | +| .NET Core 3.1 | net10.0 | No | +| .NET 5–9 | net10.0 | No | + +## .NET Task Selection Rules + +- **Rule 1 — Single task only**: Always create a **single** upgrade task that encompasses all necessary changes. The `modernize-dotnet-upgrade-engineer` agent handles the detailed breakdown during execution. +- **Rule 2 — EOL or Azure SDK incompatibility**: Create task "Upgrade .NET to latest LTS (net10.0)". Set the task `skills` array to `[]` (empty) — the upgrade agent handles execution internally. +- **Rule 3 — User-specified version**: Create task "Upgrade .NET to version X". Set the task `skills` array to `[]` (empty) — the upgrade agent handles execution internally. +- **Rule 4 — No upgrade needed**: If the project's .NET version is in support **and** the project targets .NET Framework 4.6.2+ (or any .NET Core / modern .NET) **and** the user did not request an upgrade, do **not** add an upgrade task. \ No newline at end of file diff --git a/.github/skills/create-dotnet-upgrade-plan/tasks-schema.json b/.github/skills/create-dotnet-upgrade-plan/tasks-schema.json new file mode 100644 index 000000000..397678e03 --- /dev/null +++ b/.github/skills/create-dotnet-upgrade-plan/tasks-schema.json @@ -0,0 +1,349 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Modernization Tasks Template", + "description": "Schema for tasks-template.json. Preserve field descriptions because they are used to generate the JSON by LLM.", + "type": "object", + "required": ["$schema", "description", "tasks", "metadata"], + "additionalProperties": false, + "properties": { + "$schema": { + "type": "string", + "description": "JSON Schema reference used by the template." + }, + "description": { + "type": "string", + "description": "Tasks template for modernization plan. Generate this file alongside plan.md to track individual migration tasks." + }, + "tasks": { + "type": "array", + "description": "List of individual migration tasks.", + "items": { + "oneOf": [ + { "$ref": "#/$defs/transformTask" }, + { "$ref": "#/$defs/upgradeTask" }, + { "$ref": "#/$defs/integrationTestTask" }, + { "$ref": "#/$defs/containerizationTask" }, + { "$ref": "#/$defs/deploymentTask" }, + { "$ref": "#/$defs/securityTask" }, + { "$ref": "#/$defs/infrastructureTask" } + ] + } + }, + "metadata": { + "type": "object", + "description": "Metadata for the plan.", + "additionalProperties": false, + "required": ["planName", "projectName", "language", "createdAt", "version"], + "properties": { + "planName": { + "type": "string", + "description": "[Plan name]" + }, + "projectName": { + "type": "string", + "description": "[Application Name]" + }, + "language": { + "type": "string", + "description": "[Programming language]" + }, + "createdAt": { + "type": "string", + "description": "[ISO 8601 timestamp]", + "format": "date-time" + }, + "version": { + "type": "string", + "description": "Version of the template schema.", + "const": "1.0" + } + } + } + }, + "$defs": { + "taskBase": { + "type": "object", + "additionalProperties": false, + "required": ["type", "id", "description", "requirements"], + "properties": { + "type": { + "type": "string", + "description": "Task type identifier." + }, + "id": { + "type": "string", + "description": "[Unique identifier for this task, start with sequence and category name, like '001-transform-migration-rabbitmq-to-servicebus']" + }, + "description": { + "type": "string", + "description": "[Brief description of what this task achieves from the user's perspective. It should describe high level requirements without dictating implementation details like API calls, package names, or code structure]" + }, + "reason": { + "type": "string", + "description": "[Explain why this task is needed, e.g., the motivation, business justification, or technical necessity that drives this task]" + }, + "requirements": { + "type": "string", + "description": "[The specific requirements for this task including: what to migrate (features, APIs, patterns), constraints (backward compatibility, modules to avoid), target Azure services, and technical preferences (authentication methods, patterns). It should describe high level requirements without dictating implementation details like API calls, package names, or code structure]" + }, + "environmentConfiguration": { + "type": ["string"], + "description": "[Environment configuration from user input (e.g. endpoint, access id). Omit this field if not specified]" + }, + "status": { + "type": ["string"], + "description": "Task execution status. Only has value after task is executed.", + "enum": ["pending", "started", "success", "failed", "skipped"] + }, + "taskSummary": { + "type": "string", + "description": "Summary of task execution result. Only has value after task is executed." + }, + "dependencies": { + "type": "array", + "description": "[List of task IDs that this task depends on. The task will only be executed after all its dependencies have completed successfully.]", + "items": { + "type": "string" + } + } + } + }, + "skill": { + "type": "object", + "additionalProperties": false, + "required": ["name", "location"], + "properties": { + "name": { + "type": "string", + "description": "[The skill name that will be used for this task, e.g., 'migration-rabbitmq-to-servicebus'.]" + }, + "location": { + "type": "string", + "description": "Skill location: project, remote and builtin, builtin is renamed from custom", + "enum": ["project","remote","builtin"] + } + } + }, + "successCriteria": { + "type": "object", + "additionalProperties": false, + "required": [ + "passBuild", + "generateNewUnitTests", + "passUnitTests" + ], + "description": "The task success criteria to validate after task execution.", + "properties": { + "passBuild": { + "type": ["string"], + "default": "true", + "description": "Project must compile successfully after migration, use default value if user does not specify" + }, + "generateNewUnitTests": { + "type": ["string"], + "default": "false", + "description": "Create mock-based unit tests for newly added Azure integration code to ensure test coverage, use default value if user does not specify" + }, + "passUnitTests": { + "type": ["string"], + "default": "true", + "description": "All unit tests must pass; mock dependent Azure resources if not provided, use default value if user does not specify" + } + } + }, + "successCriteriaStatus": { + "type": "object", + "not": { + "type": "null" + }, + "additionalProperties": false, + "description": "Optional validation status for each success criterion. Omit this field until validation is complete. If present, use true/false string values.", + "properties": { + "passBuild": { + "type": ["string"], + "description": "Validation status of passBuild criterion. true means passed, false means failed." + }, + "generateNewUnitTests": { + "type": ["string"], + "description": "Validation status of generateNewUnitTests criterion. true means passed, false means failed." + }, + "passUnitTests": { + "type": ["string"], + "description": "Validation status of passUnitTests criterion. true means passed, false means failed." + } + } + }, + "transformTask": { + "allOf": [ + { "$ref": "#/$defs/taskBase" }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "successCriteria"], + "properties": { + "type": { + "const": "transform", + "description": "transform task template" + }, + "skills": { + "type": "array", + "description": "The skills that will be used for this task, it is started with migration and looks like 'migration-rabbitmq-to-servicebus-mi'", + "items": { "$ref": "#/$defs/skill" } + }, + "successCriteria": { "$ref": "#/$defs/successCriteria" }, + "successCriteriaStatus": { "$ref": "#/$defs/successCriteriaStatus" } + } + } + ] + }, + "upgradeTask": { + "allOf": [ + { "$ref": "#/$defs/taskBase" }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "successCriteria"], + "properties": { + "type": { + "const": "upgrade", + "description": "Upgrade task template" + }, + "successCriteria": { "$ref": "#/$defs/successCriteria" }, + "successCriteriaStatus": { "$ref": "#/$defs/successCriteriaStatus" } + } + } + ] + }, + "containerizationTask": { + "allOf": [ + { "$ref": "#/$defs/taskBase" }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "dockerfilePath"], + "properties": { + "type": { + "const": "containerization", + "description": "Containerization task template - Only include if the target deployment requires containerization (e.g., AKS, ACA) or if the user explicitly requested containerization. Skip for non-containerized deployments." + }, + "dockerfilePath": { + "type": "string", + "description": "[Path to Dockerfile, indicate if existing or to be created]" + } + } + } + ] + }, + "deploymentTask": { + "allOf": [ + { "$ref": "#/$defs/taskBase" }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "targetAzureService", "resourceStatus", "deploymentTool", "skills"], + "properties": { + "type": { + "const": "deployment", + "description": "Deployment task template - Containerize, provision and deploy the application to Azure. Only include if the user explicitly requested deployment." + }, + "skills": { + "type": "array", + "description": "The deployment skill to use based on targetAzureService: 'azcli-aks-deploy' for Azure Kubernetes Service, 'azcli-containerapp-deploy' for Azure Container Apps, 'azcli-appservice-deploy' for Azure App Service, 'azcli-functionapp-deploy' for Azure Function App, 'azcli-appservicemi-deploy' for Azure App Service with Managed Identity", + "items": { "$ref": "#/$defs/skill" } + }, + "targetAzureService": { + "type": "string", + "description": "[Azure service name, e.g., Azure Container Apps, AKS, App Service]" + }, + "resourceStatus": { + "type": "string", + "description": "[Specify if using existing resource or will create new service]" + }, + "deploymentTool": { + "type": "string", + "description": "Infrastructure as Code tool type. Default to 'terraform' for aks, 'bicep' for other services if not specified.", + "enum": ["bicep", "terraform"] + } + } + } + ] + }, + "integrationTestTask": { + "allOf": [ + { "$ref": "#/$defs/taskBase" }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "layers"], + "properties": { + "type": { + "const": "integrationTest", + "description": "Integration test task template - Generate and run integration tests for migrated Azure services. Only include when user explicitly requests integration testing. This task runs after all transform/upgrade tasks but before containerization." + }, + "layers": { + "type": "array", + "description": "[Array of test layers to execute, e.g., [1, 2] for Layer 1 (Local Integration with TestContainers) and Layer 2 (Smoke Tests)]", + "items": { + "type": "number", + "enum": [1, 2] + }, + "minItems": 1 + } + } + } + ] + }, + "securityTask": { + "allOf": [ + { "$ref": "#/$defs/taskBase" }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "successCriteria"], + "properties": { + "type": { + "const": "security", + "description": "Security task template - Include when security remediation, vulnerability scanning, or compliance checks are required." + }, + "cveReport": { + "type": ["string"], + "description": "Path to final-cve-report.json. Only has value after CVE checking is complete." + }, + "successCriteria": { "$ref": "#/$defs/successCriteria" }, + "successCriteriaStatus": { "$ref": "#/$defs/successCriteriaStatus" } + } + } + ] + }, + "infrastructureTask": { + "allOf": [ + { "$ref": "#/$defs/taskBase" }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "skills", "iacType", "provision"], + "properties": { + "type": { + "const": "infrastructure", + "description": "Infrastructure task template - Generate IaC files (Bicep or Terraform) to provision Azure resources." + }, + "iacType": { + "type": "string", + "description": "Infrastructure as Code tool type.", + "enum": ["bicep", "terraform"] + }, + "provision": { + "type": "boolean", + "description": "Whether to provision Azure resources after generating IaC files." + }, + "skills": { + "type": "array", + "description": "The skill used for this task, e.g., 'infrastructure-bicep-generation' or 'infrastructure-terraform-generation'", + "items": { "$ref": "#/$defs/skill" } + } + } + } + ] + } + } +} diff --git a/.github/skills/create-dotnet-upgrade-plan/upgrade-plan-template.md b/.github/skills/create-dotnet-upgrade-plan/upgrade-plan-template.md new file mode 100644 index 000000000..b534fece6 --- /dev/null +++ b/.github/skills/create-dotnet-upgrade-plan/upgrade-plan-template.md @@ -0,0 +1,42 @@ +# .NET Upgrade Plan Template + +## Schema Rules + +- Use `upgradeTask` type from `tasks-schema.json` +- `successCriteria` values: **strings** (`"true"`, `"false"`) +- `skills.location`: `"builtin"` | `"project"` | `"remote"` +- `status`: `"pending"` + +## Example: tasks.json + +```json +{ + "$schema": "tasks-schema.json", + "description": ".NET version upgrade plan from .NET Framework 4.6.1 to .NET 10.0", + "tasks": [ + { + "type": "upgrade", + "id": "001-upgrade-dotnet-to-net10", + "description": "Upgrade ContosoUniversity from .NET Framework 4.6.1 to .NET 10.0", + "reason": ".NET Framework 4.6.1 reached end of support on April 26, 2022 and does not meet the minimum .NET Framework 4.6.2 requirement for Azure SDK (.NET Standard 2.0) compatibility. Upgrading to .NET 10.0 LTS ensures long-term support, access to modern APIs, and full Azure SDK compatibility.", + "requirements": "Upgrade the project from .NET Framework 4.6.1 to .NET 10.0, including project file modernization, dependency updates, and API compatibility fixes.", + "environmentConfiguration": null, + "skills": [], + "successCriteria": { + "passBuild": "true", + "generateNewUnitTests": "false", + "passUnitTests": "true", + "securityComplianceCheck": "true" + }, + "status": "pending" + } + ], + "metadata": { + "planName": "upgrade-to-lts", + "projectName": "ContosoUniversity", + "language": "dotnet", + "createdAt": "2026-02-13T00:00:00.000Z", + "version": "1.0" + } +} +``` diff --git a/.github/skills/create-java-upgrade-plan/SKILL.md b/.github/skills/create-java-upgrade-plan/SKILL.md new file mode 100644 index 000000000..fe4b54afb --- /dev/null +++ b/.github/skills/create-java-upgrade-plan/SKILL.md @@ -0,0 +1,58 @@ +--- +name: create-java-upgrade-plan +description: Create an upgrade plan to migrate a Java project to latest LTS versions +--- + +# Create Upgrade Plan + +Creates `tasks.json` (in `.metadata/` subdirectory) and `plan.md` for upgrading Java projects to target versions. + +> **Supported**: Java upgrades only. .NET and other languages are not supported. + +## ⚠️ CRITICAL: Do NOT Read Files + +**Do NOT read any workspace files** (pom.xml, build.gradle, source code, etc.). + +This plan may apply to multiple repositories not yet cloned. Generate a **generic plan** based solely on the user's prompt—never detect or mention "current versions." + +## User Input + +upgrade-prompt (Mandatory): The user's upgrade request (e.g., "Java 17", "Spring Boot 3.2", or "Upgrade to the latest LTS versions") +modernization-work-folder (Mandatory): The folder to save the upgrade plan + +## Validation + +**Valid requests**: Java/framework version upgrades, LTS migrations, dependency bumps +**Invalid requests**: Feature additions, bug fixes, refactoring, containerization, deployment, non-Java languages (.NET, Python, etc.) + +If invalid, output exactly: +``` +ERROR: The provided prompt is not a valid upgrade request. Please specify a target version (e.g., 'Java 17', 'Spring Boot 3.2'). +``` + +If unsupported language, output exactly: +``` +ERROR: Only Java upgrades are supported. The requested language is not supported. +``` +Then STOP—do not create files or ask for clarification. + +## Workflow + +1. **Parse request** → Extract target versions (defaults: Java 25, Spring Boot 4.x) +2. **Generate tasks.json** → Follow `tasks-schema.json` and `upgrade-plan-template.md`, and save it to `.metadata/tasks.json` +3. **Generate plan.md** → Brief overview of the upgrade plan + +## Task Rules + +- **Java Upgrade Task Guidelines**: You must refer to the ./java-upgrade-guideline.md for specific rules and guidelines when creating Java upgrade tasks. +- Create **only** `upgrade` type tasks +- Specify **target versions only**—never "from version X" +- Use `builtin` skills (e.g., `java-version-upgrade`) +- `successCriteria` values must be **strings** (`"true"`, not `true`) +- Set `status` to `"pending"` + +## Output + +Save to `${modernization-work-folder}/`: +- `.metadata/tasks.json` — Upgrade tasks per schema +- `plan.md` — Plan overview diff --git a/.github/skills/create-java-upgrade-plan/java-upgrade-guideline.md b/.github/skills/create-java-upgrade-plan/java-upgrade-guideline.md new file mode 100644 index 000000000..270412a96 --- /dev/null +++ b/.github/skills/create-java-upgrade-plan/java-upgrade-guideline.md @@ -0,0 +1,44 @@ +# Java Upgrade Task Guidelines + +Only add an upgrade task if the user explicitly requests it. The upgrade task must be the first task if it exists. + +## Latest Stable Versions + +- Java: 25 +- Spring Boot: 4.x +- Spring Framework: 7.x + +## Supported Upgrade Versions + +- Java: 11, 17, 21, 25 +- Spring Boot: 3.x, 4.x +- Spring Framework: 6.x, 7.x + +## Framework Compatibility + +| Spring Boot | Spring Framework | Jakarta EE | Minimum Java | Maximum Java | +|-------------|:----------------:|:----------:|:------------:|:------------:| +| 2.x | 5.x | JavaEE (javax.*) | 8 | 11 | +| 3.x | 6.x | Jakarta EE (jakarta.*) | 17 | 21 | +| 4.x | 7.x | Jakarta EE (jakarta.*) | 25 | 25 | + +## Upgrade Task Types and Included Changes + +| Task Type | Spring Framework Upgrade | Jakarta EE Migration (javax.* → jakarta.*) | JDK/Java | +|-----------|:------------------------:|:------------------------------------------:|:-----------:| +| Spring Boot 4.x upgrade | 7.x | ✓ | 25 | +| Spring Boot 3.x upgrade | 6.x | ✓ | 21 | +| Spring Framework 7.x upgrade | — | ✓ | 25 | +| Spring Framework 6.x upgrade | — | ✓ | 21 | +| Jakarta EE upgrade | — | ✓ | 21 | +| JDK/Java upgrade | — | — | to specified version | + +## Java Task Selection Rules + +When selecting the Java upgrade task type, follow these rules in order: + +- **Rule 1 — No redundant sub-tasks**: Each upgrade type (Spring Boot, Spring Framework, Jakarta EE, JDK/Java) is hierarchical — higher-level tasks already include lower-level ones. Never create a lower-level task that is already covered by a selected higher-level task. For example, if a Spring Boot 4.x upgrade task is selected (which already includes JDK 25), do NOT also create a separate JDK/Java upgrade task. +- **Rule 2 — User-specified framework request doesn't fully match system state**: When the user requests a **framework** upgrade (e.g., Spring Boot, Spring Framework) but the target version they specify is not the latest available, select the highest-level task applicable and prompt the user to clarify. For example, if the user asks to "upgrade Spring Boot to 3.x" but Spring Boot 4.x is available, create a Spring Boot 3.x upgrade task and add a clarification question asking whether they want 4.x. **This rule does NOT apply to pure JDK/Java upgrade requests — see Rule 4.** +- **Rule 3 — User-specified request matches system state**: Select the most closely matching task type that directly matches the user's request and fits the system. For example, if the user asks to "upgrade JDK" and the JDK is outdated, create a JDK/Java upgrade task — NOT a higher-level Spring Boot or Spring Framework upgrade task. +- **Rule 4 — Never upgrade other frameworks on a JDK/Java request**: When the user explicitly requests a JDK/Java upgrade, you MUST NOT upgrade Spring Boot, Spring Framework, or Jakarta EE — create only a JDK/Java upgrade task targeting the user-specified version. If the project's existing Spring Boot, Spring Framework, or Jakarta EE versions are incompatible with the target Java version (per the Framework Compatibility table above), add a clarification question asking the user whether they also want to upgrade the incompatible framework(s) to a compatible version. +- **Rule 5 — Ask for clarification when the selected task diverges from the user's request**: Whenever the task you create differs from what the user explicitly asked for (e.g., upgrading to a different version, choosing a higher-level task type, or skipping a requested change due to compatibility), you MUST use the `ask_user` tool to explain what was selected, why it differs, and ask the user to confirm or adjust. If `ask_user` is not available, add the question to the "## Open Questions" section of `plan.md`. Never silently override the user's intent. \ No newline at end of file diff --git a/.github/skills/create-java-upgrade-plan/tasks-schema.json b/.github/skills/create-java-upgrade-plan/tasks-schema.json new file mode 100644 index 000000000..397678e03 --- /dev/null +++ b/.github/skills/create-java-upgrade-plan/tasks-schema.json @@ -0,0 +1,349 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Modernization Tasks Template", + "description": "Schema for tasks-template.json. Preserve field descriptions because they are used to generate the JSON by LLM.", + "type": "object", + "required": ["$schema", "description", "tasks", "metadata"], + "additionalProperties": false, + "properties": { + "$schema": { + "type": "string", + "description": "JSON Schema reference used by the template." + }, + "description": { + "type": "string", + "description": "Tasks template for modernization plan. Generate this file alongside plan.md to track individual migration tasks." + }, + "tasks": { + "type": "array", + "description": "List of individual migration tasks.", + "items": { + "oneOf": [ + { "$ref": "#/$defs/transformTask" }, + { "$ref": "#/$defs/upgradeTask" }, + { "$ref": "#/$defs/integrationTestTask" }, + { "$ref": "#/$defs/containerizationTask" }, + { "$ref": "#/$defs/deploymentTask" }, + { "$ref": "#/$defs/securityTask" }, + { "$ref": "#/$defs/infrastructureTask" } + ] + } + }, + "metadata": { + "type": "object", + "description": "Metadata for the plan.", + "additionalProperties": false, + "required": ["planName", "projectName", "language", "createdAt", "version"], + "properties": { + "planName": { + "type": "string", + "description": "[Plan name]" + }, + "projectName": { + "type": "string", + "description": "[Application Name]" + }, + "language": { + "type": "string", + "description": "[Programming language]" + }, + "createdAt": { + "type": "string", + "description": "[ISO 8601 timestamp]", + "format": "date-time" + }, + "version": { + "type": "string", + "description": "Version of the template schema.", + "const": "1.0" + } + } + } + }, + "$defs": { + "taskBase": { + "type": "object", + "additionalProperties": false, + "required": ["type", "id", "description", "requirements"], + "properties": { + "type": { + "type": "string", + "description": "Task type identifier." + }, + "id": { + "type": "string", + "description": "[Unique identifier for this task, start with sequence and category name, like '001-transform-migration-rabbitmq-to-servicebus']" + }, + "description": { + "type": "string", + "description": "[Brief description of what this task achieves from the user's perspective. It should describe high level requirements without dictating implementation details like API calls, package names, or code structure]" + }, + "reason": { + "type": "string", + "description": "[Explain why this task is needed, e.g., the motivation, business justification, or technical necessity that drives this task]" + }, + "requirements": { + "type": "string", + "description": "[The specific requirements for this task including: what to migrate (features, APIs, patterns), constraints (backward compatibility, modules to avoid), target Azure services, and technical preferences (authentication methods, patterns). It should describe high level requirements without dictating implementation details like API calls, package names, or code structure]" + }, + "environmentConfiguration": { + "type": ["string"], + "description": "[Environment configuration from user input (e.g. endpoint, access id). Omit this field if not specified]" + }, + "status": { + "type": ["string"], + "description": "Task execution status. Only has value after task is executed.", + "enum": ["pending", "started", "success", "failed", "skipped"] + }, + "taskSummary": { + "type": "string", + "description": "Summary of task execution result. Only has value after task is executed." + }, + "dependencies": { + "type": "array", + "description": "[List of task IDs that this task depends on. The task will only be executed after all its dependencies have completed successfully.]", + "items": { + "type": "string" + } + } + } + }, + "skill": { + "type": "object", + "additionalProperties": false, + "required": ["name", "location"], + "properties": { + "name": { + "type": "string", + "description": "[The skill name that will be used for this task, e.g., 'migration-rabbitmq-to-servicebus'.]" + }, + "location": { + "type": "string", + "description": "Skill location: project, remote and builtin, builtin is renamed from custom", + "enum": ["project","remote","builtin"] + } + } + }, + "successCriteria": { + "type": "object", + "additionalProperties": false, + "required": [ + "passBuild", + "generateNewUnitTests", + "passUnitTests" + ], + "description": "The task success criteria to validate after task execution.", + "properties": { + "passBuild": { + "type": ["string"], + "default": "true", + "description": "Project must compile successfully after migration, use default value if user does not specify" + }, + "generateNewUnitTests": { + "type": ["string"], + "default": "false", + "description": "Create mock-based unit tests for newly added Azure integration code to ensure test coverage, use default value if user does not specify" + }, + "passUnitTests": { + "type": ["string"], + "default": "true", + "description": "All unit tests must pass; mock dependent Azure resources if not provided, use default value if user does not specify" + } + } + }, + "successCriteriaStatus": { + "type": "object", + "not": { + "type": "null" + }, + "additionalProperties": false, + "description": "Optional validation status for each success criterion. Omit this field until validation is complete. If present, use true/false string values.", + "properties": { + "passBuild": { + "type": ["string"], + "description": "Validation status of passBuild criterion. true means passed, false means failed." + }, + "generateNewUnitTests": { + "type": ["string"], + "description": "Validation status of generateNewUnitTests criterion. true means passed, false means failed." + }, + "passUnitTests": { + "type": ["string"], + "description": "Validation status of passUnitTests criterion. true means passed, false means failed." + } + } + }, + "transformTask": { + "allOf": [ + { "$ref": "#/$defs/taskBase" }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "successCriteria"], + "properties": { + "type": { + "const": "transform", + "description": "transform task template" + }, + "skills": { + "type": "array", + "description": "The skills that will be used for this task, it is started with migration and looks like 'migration-rabbitmq-to-servicebus-mi'", + "items": { "$ref": "#/$defs/skill" } + }, + "successCriteria": { "$ref": "#/$defs/successCriteria" }, + "successCriteriaStatus": { "$ref": "#/$defs/successCriteriaStatus" } + } + } + ] + }, + "upgradeTask": { + "allOf": [ + { "$ref": "#/$defs/taskBase" }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "successCriteria"], + "properties": { + "type": { + "const": "upgrade", + "description": "Upgrade task template" + }, + "successCriteria": { "$ref": "#/$defs/successCriteria" }, + "successCriteriaStatus": { "$ref": "#/$defs/successCriteriaStatus" } + } + } + ] + }, + "containerizationTask": { + "allOf": [ + { "$ref": "#/$defs/taskBase" }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "dockerfilePath"], + "properties": { + "type": { + "const": "containerization", + "description": "Containerization task template - Only include if the target deployment requires containerization (e.g., AKS, ACA) or if the user explicitly requested containerization. Skip for non-containerized deployments." + }, + "dockerfilePath": { + "type": "string", + "description": "[Path to Dockerfile, indicate if existing or to be created]" + } + } + } + ] + }, + "deploymentTask": { + "allOf": [ + { "$ref": "#/$defs/taskBase" }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "targetAzureService", "resourceStatus", "deploymentTool", "skills"], + "properties": { + "type": { + "const": "deployment", + "description": "Deployment task template - Containerize, provision and deploy the application to Azure. Only include if the user explicitly requested deployment." + }, + "skills": { + "type": "array", + "description": "The deployment skill to use based on targetAzureService: 'azcli-aks-deploy' for Azure Kubernetes Service, 'azcli-containerapp-deploy' for Azure Container Apps, 'azcli-appservice-deploy' for Azure App Service, 'azcli-functionapp-deploy' for Azure Function App, 'azcli-appservicemi-deploy' for Azure App Service with Managed Identity", + "items": { "$ref": "#/$defs/skill" } + }, + "targetAzureService": { + "type": "string", + "description": "[Azure service name, e.g., Azure Container Apps, AKS, App Service]" + }, + "resourceStatus": { + "type": "string", + "description": "[Specify if using existing resource or will create new service]" + }, + "deploymentTool": { + "type": "string", + "description": "Infrastructure as Code tool type. Default to 'terraform' for aks, 'bicep' for other services if not specified.", + "enum": ["bicep", "terraform"] + } + } + } + ] + }, + "integrationTestTask": { + "allOf": [ + { "$ref": "#/$defs/taskBase" }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "layers"], + "properties": { + "type": { + "const": "integrationTest", + "description": "Integration test task template - Generate and run integration tests for migrated Azure services. Only include when user explicitly requests integration testing. This task runs after all transform/upgrade tasks but before containerization." + }, + "layers": { + "type": "array", + "description": "[Array of test layers to execute, e.g., [1, 2] for Layer 1 (Local Integration with TestContainers) and Layer 2 (Smoke Tests)]", + "items": { + "type": "number", + "enum": [1, 2] + }, + "minItems": 1 + } + } + } + ] + }, + "securityTask": { + "allOf": [ + { "$ref": "#/$defs/taskBase" }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "successCriteria"], + "properties": { + "type": { + "const": "security", + "description": "Security task template - Include when security remediation, vulnerability scanning, or compliance checks are required." + }, + "cveReport": { + "type": ["string"], + "description": "Path to final-cve-report.json. Only has value after CVE checking is complete." + }, + "successCriteria": { "$ref": "#/$defs/successCriteria" }, + "successCriteriaStatus": { "$ref": "#/$defs/successCriteriaStatus" } + } + } + ] + }, + "infrastructureTask": { + "allOf": [ + { "$ref": "#/$defs/taskBase" }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "skills", "iacType", "provision"], + "properties": { + "type": { + "const": "infrastructure", + "description": "Infrastructure task template - Generate IaC files (Bicep or Terraform) to provision Azure resources." + }, + "iacType": { + "type": "string", + "description": "Infrastructure as Code tool type.", + "enum": ["bicep", "terraform"] + }, + "provision": { + "type": "boolean", + "description": "Whether to provision Azure resources after generating IaC files." + }, + "skills": { + "type": "array", + "description": "The skill used for this task, e.g., 'infrastructure-bicep-generation' or 'infrastructure-terraform-generation'", + "items": { "$ref": "#/$defs/skill" } + } + } + } + ] + } + } +} diff --git a/.github/skills/create-java-upgrade-plan/upgrade-plan-template.md b/.github/skills/create-java-upgrade-plan/upgrade-plan-template.md new file mode 100644 index 000000000..144ae61e4 --- /dev/null +++ b/.github/skills/create-java-upgrade-plan/upgrade-plan-template.md @@ -0,0 +1,51 @@ +# Upgrade Plan Template + +> **Important**: Specify only TARGET versions—never "current" or "from" versions. Do NOT read project files. + +## Schema Rules + +- Use `upgradeTask` type from `tasks-schema.json` +- `successCriteria` values: **strings** (`"true"`, `"false"`) +- `skills.location`: `"builtin"` | `"project"` | `"remote"` +- `status`: `"pending"` + +## Example: tasks.json +{ + "description": "Upgrade plan to migrate project to latest LTS versions", + "tasks": [ + { + "type": "upgrade", + "id": "001-upgrade-java-spring-boot", + "description": "Upgrade to Java 25 and Spring Boot 4.x", + "requirements": "Upgrade JDK to 25, Spring Boot to 4.x, Spring Framework to 7.x, and migrate javax.* to jakarta.* if needed", + "environmentConfiguration": null, + "skills": [{ "name": "java-version-upgrade", "location": "builtin" }], + "successCriteria": { + "passBuild": "true", + "generateNewUnitTests": "false", + "passUnitTests": "true" + }, + "status": "pending" + } + ], + "metadata": { + "planName": "upgrade-to-lts", + "projectName": "application", + "language": "java", + "createdAt": "2026-02-06T00:00:00.000Z", + "version": "1.0" + } +} +``` + +## Example: plan.md + +```markdown +# Upgrade Plan + +## Overview +Upgrade to latest LTS versions. + +## Tasks +See the .metadata/tasks.json for detailed task breakdown. +``` diff --git a/.github/skills/create-modernization-plan/SKILL.md b/.github/skills/create-modernization-plan/SKILL.md new file mode 100644 index 000000000..13ea71ceb --- /dev/null +++ b/.github/skills/create-modernization-plan/SKILL.md @@ -0,0 +1,129 @@ +--- +name: create-modernization-plan +description: Create a modernization plan to migrate the project to Azure +--- + +# Create modernization plan + +This skill is used to create a modernization plan to migrate the a given project to Azure + +## User Input + +modernization-prompt: The user input to generate the modernization plan +modernization-work-folder (Mandatory): The folder to save the modernization plan +github-issue-link (Optional): A github issue to track the modernization status, to be filled into plan template +assessment-report (Optional): A assessment report for the project will be modernized, it will provide the data about the project for modernization +plan-name (Optional): The plan name to be filled into plan template +language (Mandatory): The programming language of the project (java or dotnet) + +## Supported Task Patterns + +Read the supported patterns file based on the language: +- For .NET projects: Read `supported-patterns-dotnet.md` +- For all other projects: Read `supported-patterns-java.md`. Default option. + +These files contain the list of supported task patterns with and without skill definitions. If a skill is available, the skill location should be set to `builtin`. + +## Workflow + +Given the user input, do this: + +1. Double Check the issues + **IMPORTANT**: + - If you are given an assessment-report, you need to double check if the issue really exist in current project. If not, please ignore this issue when you generate the plan + +2. **Load context**: Retrieve information for plan, you can read + 1) Analysis the supported patterns to find the right tasks for the issues + 2) Analysis modernization requirement from user input + +3. **Clarification and Questionnaire** (only when the `ask_user` tool is available): If there are any open issues or ambiguities that need user input, use the following steps to answer any questions. Also ask the questions outlined in `questionnaire.md` via `ask_user` tool to scope the modernization plan. For questionnaire questions, if the user input already provides the answer, skip asking that question and use the provided information as the answer. + 1) Use the `ask_user` tool to ask the user each clarification question directly. Wait for the user's response before proceeding. + 2) Record each question and answer for use in the summary step. + 3) If the `ask_user` tool is not available, skip this step entirely and proceed to plan generation using best-effort defaults. + +4. **Summary & Confirmation** (only when the `ask_user` tool is available): + 1) Present a summary to the user via `ask_user` that includes: + - All clarification questions and the user's answers (if any were asked in step 3) + - The planned task list with key details for each task: task name, type, matched skill/pattern, and a brief description of what it will do + 2) Ask the user to confirm the summary is correct, or provide additional input to adjust any answers or task list. + 3) If the user provides additional input, incorporate the changes. If the user chooses to skip or confirms, proceed to plan generation. + 4) If the `ask_user` tool is not available, skip this step entirely. + +5. **Generate plan and tasks**: Generate plan.md and tasks.json using the appropriate templates: + + **Template Selection**: + - Use **plan-template.md** for code migration, containerization, and deployment tasks + - Use **security-plan-template.md** to include a security/CVE remediation task in every modernization plan. + - Use **infra-plan-template.md** ONLY when user explicitly requests infrastructure (e.g., "prepare infrastructure", "create landing zone", "provision resources", "generate Bicep/Terraform") + + **Plan Generation**: + 1) Follow the structure of the selected template to generate the plan + 2) Follow the rules defined in the template to fill in the sections with relevant information based on the analysis of user input and content of mentioned files + 3) Save the plan in folder ${modernization-work-folder} with the filename plan.md. If a plan already exists, overwrite it. + 4) Generate a separate tasks.json file following the tasks-schema.json schema with all upgrade, transform, security, integration test, infrastructure, containerization, and deployment tasks + 5) Save the tasks in folder ${modernization-work-folder}/.metadata/ with the filename tasks.json. If tasks.json already exists, overwrite it. + + **Clarification Outcomes in Plan**: Incorporate all clarification answers from steps 3–4 into `plan.md` and `tasks.json`: + - Update the relevant task's `requirements` or `description` in `tasks.json` based on the answer. Do NOT create a separate task for an implementation detail—only add a new task when the answer introduces entirely new migration scope. + - Record all clarification questions and their outcomes in the **"## Open Questions & Questionnaire"** section of `plan.md`: + - Answered: `- [x] Q: ... → A: ...` + - Unanswered/skipped: `- [ ] ...` + - Remove the section entirely if no clarification questions were raised. + + **IMPORTANT**: The plan.md should NOT contain the detailed task breakdown. Those details go into tasks.json for better tracking and programmatic access. + + **Task Breakdown Rules**: When creating tasks for tasks.json and plan.md: + - Purpose: Break down coding work into discrete migration tasks. Each task represents a user-requested migration from one service/component to another, or a specific business logic modernization. + - Create tasks ONLY based on what the user explicitly requested - do not infer or add implicit tasks, **except** for the security/CVE remediation task which must always be included in every plan + - If an `assessment-report` is provided, the task description must identify which specific issues from the assessment report are addressed by that task (e.g., "Addresses issues: , ") + - Group related changes that serve a single user goal into one task (e.g., all changes needed to migrate to PostgreSQL) + - Find a matched skill / pattern for the task, following the following priority order. + 1. Skills available for the project, which will be listed in the `skill` tool description. + 2. Patterns that will be attached and available at plan execution phase, listed in the supported patterns file. + 3. Otherwise if no relevant pattern is available for the task pattern, use the prompt segment from the user directly. DO NOT expand the request scope. + - **IMPORTANT**: + - You MUST NOT use the pattern name as the skill name in the generated plan and tasks.json. + - If there are similar skills defined in project skill `.github/skills/` versus other skills, MUST use the one defined in project. + - Skills must be fully matched. For migration scenarios, both the source product and target product must match the task intent. + - Each task should be independently testable with integration tests + - Do not add tests for unimpacted code or existing functionality unless user requested + - **IMPORTANT**: Do NOT read individual skill files at this stage; Do Not include the skill detail in the tasks. + + **Integration Test Task Rules**: When user explicitly requests integration testing (e.g., "add integration tests", "generate integration tests", "test the migration"): + - Add an integration test task with type "integrationTest" after all transform/upgrade tasks but before containerization tasks + - This integration test task should: + - Have id format: "{sequence}-integrationTest" where sequence is the next number after the last migration task (e.g., if last migration is 001, use "002-integrationTest") + - Have description: "Generate and run integration tests for Azure service migrations" + - Have dependencies on ALL transform and upgrade task ids (so it runs after all migrations are complete) + - Have requirements: "Generate Layer 1 (Local Integration with TestContainers) and Layer 2 (Smoke Tests) for all Azure service migrations" + - Have layers: [1, 2] (only Layer 1 and Layer 2 tests) + - Omit environmentConfiguration unless explicitly provided by user input + - The integration test task appears in plan.md as a separate section after migration tasks but before containerization + + **Java Upgrade Task Guidelines**: Only add an upgrade task if the user explicitly requests it. You must refer to the ./java-upgrade-guideline.md for specific rules and guidelines when creating Java upgrade tasks. + + **.NET Upgrade Task Guidelines**: You must refer to the ./dotnet-upgrade-guideline.md for specific rules and guidelines when creating .NET upgrade tasks. + + **Deployment Task Rules**: + - **IMPORTANT** Do NOT create task type with `containerization` if deployment task already exists, deployment task will cover the containerization work if needed. + - Deployment Task Options: Azure App Service, Azure Kubernetes Service, Azure Container Apps (default), Azure App Service Managed Instance, Azure Static Web App, Azure Function App + + **Security Task Guidelines**: The security task order should be after all the upgrade and transform tasks and before the deployment tasks in the generated plan. If the user provides specific security requirements, incorporate them into the security task; otherwise, use the default requirements from the template. + + **IMPORTANT**: The upgrade task must be the first task in the task list because subsequent transform tasks (e.g., migrating to Azure services) depend on the upgraded runtime and project format. + +6. **Rulebook Compliance Validation** (only when rulebook attachments are present): + After generating the plan and tasks, call skill `validate-rulebook-compliance` to validate that the plan tasks cover the rulebook rules: + - tasks-json-path: `${modernization-work-folder}/.metadata/tasks.json` + - compliance-output-path: `${modernization-work-folder}/rulebook-compliance.md` + - This validation is **best-effort only** and must **not** block or fail plan creation. + - If the validation call cannot run, fails, or required context/attachments are missing, you must still complete the workflow and emit `${modernization-work-folder}/plan.md` and `${modernization-work-folder}/.metadata/tasks.json`. + - If validation cannot be completed successfully, write a minimal warning/status report to `${modernization-work-folder}/rulebook-compliance.md` explaining that validation was skipped or failed and why, if known. + +## Completion Criteria + +1. All clarification & questionnaire questions have been asked (or skipped with defaults) via `ask_user`, answers incorporated into `plan.md` and `tasks.json`, and outcomes recorded in the "## Open Questions & Questionnaire" section of `plan.md` +2. The modernization task list is built +3. The modernization task list MUST be scoped according to user input +4. DON'T RUN the plan if user does not explicitly ask you to run the plan +5. The generated plan.md and tasks.json are saved in the specified folder `${modernization-work-folder}` \ No newline at end of file diff --git a/.github/skills/create-modernization-plan/dotnet-upgrade-guideline.md b/.github/skills/create-modernization-plan/dotnet-upgrade-guideline.md new file mode 100644 index 000000000..a5c97e03a --- /dev/null +++ b/.github/skills/create-modernization-plan/dotnet-upgrade-guideline.md @@ -0,0 +1,43 @@ +# .NET Upgrade Task Guidelines + +Only add a .NET upgrade task if one of the following conditions is met: + +1. **End of Life (EOL)**: The project's .NET version is out of mainstream support. +2. **Azure SDK Compatibility**: The project targets a .NET Framework version older than 4.6.2, which does not support `netstandard2.0` and cannot use modern Azure SDK (`Azure.*`) packages. +3. **User Request**: The user explicitly requests a .NET version upgrade. + +The upgrade task must be the first task if it exists. + +### Target Version + +Always upgrade to the **latest LTS** version unless the user explicitly specifies a different target version. + +- Current latest LTS: **.NET 10** (`net10.0`) + +### Azure SDK Minimum .NET Version + +The modern Azure SDK for .NET (`Azure.*` packages) targets `netstandard2.0` as its baseline. The following .NET Framework versions do **not** support `netstandard2.0` and require an upgrade: + +| .NET Framework Version | `netstandard2.0` Support | Action | +|------------------------|:------------------------:|--------| +| 4.5 and below | ❌ | Upgrade required | +| 4.6 | ❌ | Upgrade required | +| 4.6.1 | ⚠️ Unreliable | Upgrade recommended | +| 4.6.2+ | ✅ | No upgrade needed for Azure SDK compatibility | + +> **Note**: .NET Framework 4.6.1 is technically listed as supporting `netstandard2.0` but has known issues. Microsoft recommends 4.7.2+ for reliable support. The Azure SDK explicitly targets `net462` as its minimum. + +## Framework Compatibility + +| Source Framework | Target Framework | SDK-Style Conversion Required | +|-----------------|:----------------:|:----------------------------:| +| .NET Framework 4.x | net10.0 | Yes | +| .NET Core 3.1 | net10.0 | No | +| .NET 5–9 | net10.0 | No | + +## .NET Task Selection Rules + +- **Rule 1 — Single task only**: Always create a **single** upgrade task that encompasses all necessary changes. The `modernize-dotnet-upgrade-engineer` agent handles the detailed breakdown during execution. +- **Rule 2 — EOL or Azure SDK incompatibility**: Create task "Upgrade .NET to latest LTS (net10.0)". Set the task `skills` array to `[]` (empty) — the upgrade agent handles execution internally. +- **Rule 3 — User-specified version**: Create task "Upgrade .NET to version X". Set the task `skills` array to `[]` (empty) — the upgrade agent handles execution internally. +- **Rule 4 — No upgrade needed**: If the project's .NET version is in support **and** the project targets .NET Framework 4.6.2+ (or any .NET Core / modern .NET) **and** the user did not request an upgrade, do **not** add an upgrade task. \ No newline at end of file diff --git a/.github/skills/create-modernization-plan/infra-plan-template.md b/.github/skills/create-modernization-plan/infra-plan-template.md new file mode 100644 index 000000000..a0bf419fb --- /dev/null +++ b/.github/skills/create-modernization-plan/infra-plan-template.md @@ -0,0 +1,48 @@ +# Infrastructure Plan Template + +Use this template when user explicitly requests infrastructure preparation (e.g., "prepare infrastructure", "create landing zone", "provision resources", "generate Bicep/Terraform/IaC files"). + +--- + +# Infrastructure Plan: [Plan Title] + +## User Requirements + +[A concise summary of the user's inputs, requirements and preferences for the infrastructure, may include: project codes, assessment report, architecture diagram] + +**Plan Configuration**: + +| Parameter | Value | Description | +|-----------|-------|-------------| +| IaC Tool | [bicep (default) / terraform] | Infrastructure as Code tool | +| Provision | [true (default) / false] | Whether to provision resources after generating IaC | +| Subscription | [Azure subscription ID] | Target Azure subscription | + +--- + +## Proposed Architecture + +[A high-level text diagram illustrating the proposed Azure resource architecture that meets the user's requirements] + +--- + +## Azure Resource List +A complete list of Azure resources to be generated. + +| Resource Type | Resource Name | SKU | Purpose | +|---------------|---------------|-----|---------| +| [e.g., SQL Database] | [e.g., sqldb-myapp-prod] | [e.g., S1] | [Purpose] | + +--- + +## Task + +**Description**: Generate IaC files to provision the required Azure resources. + +**Output**: Files of infrastructure as code + +**Skill**: [infrastructure-bicep-generation | infrastructure-terraform-generation] + +**Success Criteria**: +- IaC files generated and validated +- Resources provisioned successfully (if Provision=true) diff --git a/.github/skills/create-modernization-plan/java-upgrade-guideline.md b/.github/skills/create-modernization-plan/java-upgrade-guideline.md new file mode 100644 index 000000000..270412a96 --- /dev/null +++ b/.github/skills/create-modernization-plan/java-upgrade-guideline.md @@ -0,0 +1,44 @@ +# Java Upgrade Task Guidelines + +Only add an upgrade task if the user explicitly requests it. The upgrade task must be the first task if it exists. + +## Latest Stable Versions + +- Java: 25 +- Spring Boot: 4.x +- Spring Framework: 7.x + +## Supported Upgrade Versions + +- Java: 11, 17, 21, 25 +- Spring Boot: 3.x, 4.x +- Spring Framework: 6.x, 7.x + +## Framework Compatibility + +| Spring Boot | Spring Framework | Jakarta EE | Minimum Java | Maximum Java | +|-------------|:----------------:|:----------:|:------------:|:------------:| +| 2.x | 5.x | JavaEE (javax.*) | 8 | 11 | +| 3.x | 6.x | Jakarta EE (jakarta.*) | 17 | 21 | +| 4.x | 7.x | Jakarta EE (jakarta.*) | 25 | 25 | + +## Upgrade Task Types and Included Changes + +| Task Type | Spring Framework Upgrade | Jakarta EE Migration (javax.* → jakarta.*) | JDK/Java | +|-----------|:------------------------:|:------------------------------------------:|:-----------:| +| Spring Boot 4.x upgrade | 7.x | ✓ | 25 | +| Spring Boot 3.x upgrade | 6.x | ✓ | 21 | +| Spring Framework 7.x upgrade | — | ✓ | 25 | +| Spring Framework 6.x upgrade | — | ✓ | 21 | +| Jakarta EE upgrade | — | ✓ | 21 | +| JDK/Java upgrade | — | — | to specified version | + +## Java Task Selection Rules + +When selecting the Java upgrade task type, follow these rules in order: + +- **Rule 1 — No redundant sub-tasks**: Each upgrade type (Spring Boot, Spring Framework, Jakarta EE, JDK/Java) is hierarchical — higher-level tasks already include lower-level ones. Never create a lower-level task that is already covered by a selected higher-level task. For example, if a Spring Boot 4.x upgrade task is selected (which already includes JDK 25), do NOT also create a separate JDK/Java upgrade task. +- **Rule 2 — User-specified framework request doesn't fully match system state**: When the user requests a **framework** upgrade (e.g., Spring Boot, Spring Framework) but the target version they specify is not the latest available, select the highest-level task applicable and prompt the user to clarify. For example, if the user asks to "upgrade Spring Boot to 3.x" but Spring Boot 4.x is available, create a Spring Boot 3.x upgrade task and add a clarification question asking whether they want 4.x. **This rule does NOT apply to pure JDK/Java upgrade requests — see Rule 4.** +- **Rule 3 — User-specified request matches system state**: Select the most closely matching task type that directly matches the user's request and fits the system. For example, if the user asks to "upgrade JDK" and the JDK is outdated, create a JDK/Java upgrade task — NOT a higher-level Spring Boot or Spring Framework upgrade task. +- **Rule 4 — Never upgrade other frameworks on a JDK/Java request**: When the user explicitly requests a JDK/Java upgrade, you MUST NOT upgrade Spring Boot, Spring Framework, or Jakarta EE — create only a JDK/Java upgrade task targeting the user-specified version. If the project's existing Spring Boot, Spring Framework, or Jakarta EE versions are incompatible with the target Java version (per the Framework Compatibility table above), add a clarification question asking the user whether they also want to upgrade the incompatible framework(s) to a compatible version. +- **Rule 5 — Ask for clarification when the selected task diverges from the user's request**: Whenever the task you create differs from what the user explicitly asked for (e.g., upgrading to a different version, choosing a higher-level task type, or skipping a requested change due to compatibility), you MUST use the `ask_user` tool to explain what was selected, why it differs, and ask the user to confirm or adjust. If `ask_user` is not available, add the question to the "## Open Questions" section of `plan.md`. Never silently override the user's intent. \ No newline at end of file diff --git a/.github/skills/create-modernization-plan/plan-template.md b/.github/skills/create-modernization-plan/plan-template.md new file mode 100644 index 000000000..907521214 --- /dev/null +++ b/.github/skills/create-modernization-plan/plan-template.md @@ -0,0 +1,70 @@ +# Modernization Plan Template + +Use this template to generate modernization plans for applications. Replace placeholders with actual values and customize content based on the specific modernization scenario. + +**Planning Philosophy**: +- **Focus on GOALS, not implementation**: Describe WHAT needs to be achieved from the user's perspective +- **Skills define HOW**: All implementation details (frameworks, SDKs, libraries, code patterns, authentication methods) will be determined by the referenced skills +- **No assumptions**: Do not assume or specify any technical implementation approach in the plan - let skills handle all "how" decisions + +--- + +# Modernization Plan: [Modernization Title] + +**Project**: [Application Name] + +--- + +## Technical Framework + +**Purpose**: Document the application's current technology stack to provide context for the migration. + +**Template**: +```markdown +- **Language**: [Programming language and version, e.g., Java 11, Python 3.9, .NET 6] +- **Framework**: [Application framework and version, e.g., Spring Boot 2.7.18, Django 4.2, ASP.NET Core 6.0] +- **Build Tool**: [Build system, e.g., Maven 3.9, Gradle 8.0, npm] +- **Database**: [Current database, e.g., Oracle 19c, PostgreSQL 14, SQL Server 2019] +- **Key Dependencies**: [Major libraries/frameworks, e.g., Spring Data JPA, Hibernate, Entity Framework] +``` + +--- + +## Overview + +**Purpose**: Describe the high-level modernization goals without technical details. Focus on business objectives and what will change. + +**Template**: +> This migration [describe what is being migrated]. The application currently [describe current state]. The new architecture will: +> +> - [First key change and its business benefit] +> - [Second key change and its business benefit] +> - [Third key change and its business benefit] +> +> The migration follows [describe phased approach without technical specifics]. + +--- + +## Migration Impact Summary + +**Purpose**: Create a simple table showing migration impact for each application and its affected services. +**Rule**: Each line should be limited to a maximum of 80 characters. +**Template**: +``` +| Application | Original Service | New Azure Service | Authentication | Comments | +|-------------|------------------|-------------------|-------------|----------------|----------| +| [App Name] | [Old Service] | [Azure Service] | [Auth method (Default is Managed Identity if user not specified)] | [User specified request for the migration] | +``` + +--- + +## Open Questions & Questionnaire + +**Purpose**: Record all clarification questions raised during plan creation and their resolution status. Record all answers to questionnaire questions. + +**Template**: +```markdown +- [x] Q: What authentication method should be used for Azure Storage? → A: Use managed identity +- [ ] Which region should the deployment target? +``` + diff --git a/.github/skills/create-modernization-plan/questionnaire.md b/.github/skills/create-modernization-plan/questionnaire.md new file mode 100644 index 000000000..7a87a9bfa --- /dev/null +++ b/.github/skills/create-modernization-plan/questionnaire.md @@ -0,0 +1,19 @@ +# Try to answer these from the user context, and otherwise ask the user where required. + +1. Deployment to Azure +* Azure Container Apps (must include containerization) +* Azure Kubernetes Service (must include containerization) +* Azure App Service +* Azure App Service Managed Instance +* Azure Function App +* Azure Static Web App +* (Default) No deployment + +2. Should the Modernization Plan include Integration Testing? +* Yes, Local Integration with Containers +* Yes, Local Integration and Smoke Tests +* (Default) No + +3. Should the Modernization Plan include Containerization (Dockerfile generation)? +* Yes +* (Default) No \ No newline at end of file diff --git a/.github/skills/create-modernization-plan/security-plan-template.md b/.github/skills/create-modernization-plan/security-plan-template.md new file mode 100644 index 000000000..3a641b72a --- /dev/null +++ b/.github/skills/create-modernization-plan/security-plan-template.md @@ -0,0 +1,26 @@ +## Security Compliance + +**Purpose**: Scan and remediate CVEs (Common Vulnerabilities and Exposures) in project dependencies to ensure the modernized application is free of known security vulnerabilities. + +**Condition**: Always include this task in every modernization plan. Do not include this task if the user explicitly requests that it be removed. + +**Template**: + +**Description**: Scan all project dependencies for known CVEs and remediate any identified vulnerabilities to ensure the application is secure before deployment. + +**Requirements**: + Upgrade vulnerable dependencies to the minimum patched version. If a CVE fix requires a major version upgrade, document the affected dependency, the current version, the upgraded major version, and the breaking change risk. Verify that the project builds and all tests pass after remediation. + If the user provided specific security requirements, incorporate them as well. + +**Environment Configuration**: + Runtime environment established by previous tasks (e.g., Java Home, .NET runtime). + Build tool established by previous tasks (e.g., Maven/Gradle, dotnet). + +**App Scope**: + The app folders that this task will operate on + +**Skills**: + - Skill Name: validate-cves-and-fix + - Skill Location: builtin + - Skill Name: [additional skill if needed] + - Skill Location: [Skill location] \ No newline at end of file diff --git a/.github/skills/create-modernization-plan/supported-patterns-dotnet.md b/.github/skills/create-modernization-plan/supported-patterns-dotnet.md new file mode 100644 index 000000000..61212775b --- /dev/null +++ b/.github/skills/create-modernization-plan/supported-patterns-dotnet.md @@ -0,0 +1,50 @@ +## Supported Task Patterns + +The following are the task patterns supported by the modernize CLI. These patterns are used to identify the modernization tasks that need to be performed based on the user's input. + +The patterns are categorized into two groups, and they should be treated differently if picked: + +* Patterns with skill definitions: These patterns have pre-defined skills that can be used to execute the tasks. If a task matches one of these patterns, the corresponding skill should be used in the task plan. +* Patterns without skill definitions: These patterns do not have pre-defined skills. If a task matches one of these patterns, the description should be used to guide the AI in performing the required tasks. + **IMPORTANT**: The pattern name should NEVER be used as the skill name in the generated plan and tasks.json. They are meant to guide the task generation, not to be directly used as skills. + + +### Task Patterns with Skill Definitions +These patterns have pre-defined skills to assist in their execution. When they are selected in a modernization plan, the corresponding skills should be used. +Each of the item is written in the following format: `- **skill-name**: skill-description`. + +- **azcli-aks-deploy**: Generate plan for deploying to existing Azure Resources for Azure Kubernetes Service, using azcli +- **azcli-appservice-deploy**: Deployment steps for Azure App Service under the AzCLI flow +- **azcli-appservicemi-deploy**: Deployment steps for Azure App Service Managed Identity under the AzCLI flow +- **azcli-containerapp-deploy**: Generate plan for deploying to existing Azure Resources for Azure Container Apps, using azcli +- **azcli-functionapp-deploy**: Deployment steps for Azure Function App under the AzCLI flow +- **containerization**: Setup Dockerfiles for the project to run inside of containers for Azure Container Apps or Azure Kubernetes Service. +- **infrastructure-bicep-generation**: Generate Bicep IaC files for Azure infrastructure provisioning +- **infrastructure-terraform-generation**: Generate Terraform IaC files for Azure infrastructure provisioning +- **migration-azure-communication-email**: This knowledge base provides knowledge about how to use Azure Communication Services for sending emails in .NET applications, covering authentication with Managed Identity, configuration, and email operations. +- **migration-azure-confluent-kafka**: This knowledge base provides knowledge about migrating .NET applications using Confluent.Kafka from local Kafka to Confluent Cloud on Azure, using Azure Managed Identity and DefaultAzureCredential. +- **migration-azure-database-postgresql**: This file provides guidance for migrating .NET applications to Azure Database for PostgreSQL with passwordless Managed Identity, covering both Entity Framework Core and non-EF Core applications. +- **migration-azure-eventhubs-kafka**: This knowledge base provides knowledge about migrating .NET applications using Confluent.Kafka from local Kafka to Azure Event Hubs for Kafka, using Azure Managed Identity and DefaultAzureCredential. +- **migration-azure-keyvault-certificate**: This file provides guidance on using Azure Key Vault certificate management in .NET, covering authentication, configuration, and certificate operations. +- **migration-azure-keyvault-secret**: This file provides guidance on using Azure Key Vault secrets in .NET, covering authentication, configuration, and secret operations. +- **migration-azure-redis-cache**: This knowledge base provides guidance for adding distributed caching with Azure Cache for Redis to .NET applications, or migrating existing caching implementations (in-memory cache, Redis, or other providers) to use Azure Cache for Redis with DefaultAzureCredential (Managed Identity) authentication. +- **migration-azure-servicebus**: This knowledge base provides knowledge about migrating .NET applications to use Azure Service Bus with Managed Identity, covering queue and topic operations, message handling, and processor configurations. +- **migration-azure-sql-database**: This knowledge base provides knowledge about migrating .NET applications to use Azure SQL Database and Azure SQL Managed Instance with Managed Identity authentication, including Entity Framework 6 provider configuration. +- **migration-azure-storage-blob**: This knowledge base provides comprehensive knowledge about using Azure Storage Blob SDK in .NET applications, covering Managed Identity authentication, blob operations, container management, and SAS token generation. +- **migration-azure-storage-mount**: This knowledge base provides knowledge about migrating .NET applications from using hard-coded local file paths to Azure mounted storage paths while maintaining the same functionality. +- **migration-console-logging**: This knowledge base provides knowledge about configuring console logging in .NET applications for cloud environments, ensuring proper log output for container and cloud platform log aggregation. +- **migration-dependency-management**: Comprehensive guide for understanding and implementing dependency management in .NET projects, covering both modern SDK-style projects (.NET Core, .NET 5+) and legacy .NET Framework projects. Includes package reference management, project file structure, and migration considerations. +- **migration-managed-identity**: Comprehensive guidance for migrating .NET applications to use Azure Managed Identity for authentication instead of connection strings, client secrets, certificates, or other credential-based authentication methods. Covers Azure SQL, Storage, Key Vault, Service Bus, Event Hubs, Cosmos DB, and more. +- **migration-microsoft-entra-id**: This knowledge base provides knowledge about migrating .NET applications to use Microsoft Entra ID (formerly Azure AD) for authentication, including ASP.NET Core, ASP.NET Web Forms, and Microsoft Graph integration. +- **migration-opentelemetry-azure**: This guide describes how to implement OpenTelemetry in .NET with Azure Monitor for tracing, metrics, and logging migration. + +### Task Patterns without Skill Definitions +These patterns DO NOT have pre-defined skills. The pattern name and description define the modernization scenario, NOT A SKILL. They are in the format of `- **pattern-name**: pattern-description`. + +A pattern should be selected if it matches one of the customer's requirements, and there are no skills supporting this requirement. + +**IMPORTANT**: +- NEVER write the pattern name as skill name in the generated plan. +- Tasks generated from these patterns must have NO skill assigned. Do not reuse any skill from the "Task Patterns with Skill Definitions" section, even if a skill targets a similar technology or appears related. + + diff --git a/.github/skills/create-modernization-plan/supported-patterns-java.md b/.github/skills/create-modernization-plan/supported-patterns-java.md new file mode 100644 index 000000000..6ae2dee44 --- /dev/null +++ b/.github/skills/create-modernization-plan/supported-patterns-java.md @@ -0,0 +1,88 @@ +## Supported Task Patterns + +The following are the task patterns supported by the modernize CLI. These patterns are used to identify the modernization tasks that need to be performed based on the user's input. + +The patterns are categorized into two groups, and they should be treated differently if picked: + +* Patterns with skill definitions: These patterns have pre-defined skills that can be used to execute the tasks. If a task matches one of these patterns, the corresponding skill should be used in the task plan. +* Patterns without skill definitions: These patterns do not have pre-defined skills. If a task matches one of these patterns, the description should be used to guide the AI in performing the required tasks. + **IMPORTANT**: The pattern name should NEVER be used as the skill name in the generated plan and tasks.json. They are meant to guide the task generation, not to be directly used as skills. + + +### Task Patterns with Skill Definitions +These patterns have pre-defined skills to assist in their execution. When they are selected in a modernization plan, the corresponding skills should be used. +Each of the item is written in the following format: `- **skill-name**: skill-description`. + +- **azcli-aks-deploy**: Generate plan for deploying to existing Azure Resources for Azure Kubernetes Service, using azcli +- **azcli-appservice-deploy**: Deployment steps for Azure App Service under the AzCLI flow +- **azcli-appservicemi-deploy**: Deployment steps for Azure App Service Managed Identity under the AzCLI flow +- **azcli-containerapp-deploy**: Generate plan for deploying to existing Azure Resources for Azure Container Apps, using azcli +- **azcli-functionapp-deploy**: Deployment steps for Azure Function App under the AzCLI flow +- **containerization**: Setup Dockerfiles for the project to run inside of containers for Azure Container Apps or Azure Kubernetes Service. +- **infrastructure-bicep-generation**: Generate Bicep IaC files for Azure infrastructure provisioning +- **infrastructure-terraform-generation**: Generate Terraform IaC files for Azure infrastructure provisioning +- **migration-AWS-secrets-manager-to-azure-key-vault**: Migrates Java applications from AWS Secrets Manager (SecretsManagerClient, AWSSecretsManager) to Azure Key Vault SecretClient for secrets management. Use when migrating Java projects from AWS to Azure, replacing AWS secret storage with Azure Key Vault, or modernizing cloud secret management. +- **migration-activemq-servicebus**: Migrates Java Spring Boot applications from ActiveMQ JMS messaging to Azure Service Bus JMS. Replaces ActiveMQ dependencies with Spring Cloud Azure Service Bus JMS starter and updates connection configuration. Use when migrating Spring JMS applications from ActiveMQ to Azure Service Bus or modernizing on-premises messaging to Azure. +- **migration-amqp-rabbitmq-servicebus**: Migrates Java Spring applications from RabbitMQ AMQP messaging to Azure Service Bus via Spring Messaging. Replaces Spring AMQP RabbitMQ dependencies, updates connection settings, and migrates message producers and consumers. Use when migrating Spring applications from RabbitMQ to Azure Service Bus or replacing on-premises message brokers with Azure managed messaging. +- **migration-ant-project-to-maven-project**: Migrates Java projects from Ant build system to Maven, converting build.xml to pom.xml and restructuring directories to Maven standard layout. Use when modernizing Java projects that use Ant builds, converting to Maven for dependency management, or standardizing build tooling. +- **migration-auth-by-mi-for-azure-redis-in-micronaut-project**: Secure Azure Cache for Redis with Managed Identity via Micronaut +- **migration-certificate-management-to-azure-key-vault**: Migrates Java TLS/MTLS certificate management from local KeyStore storage to Azure Key Vault JCA (Java Cryptography Architecture). Replaces local certificate handling with Azure Key Vault for centralized certificate management. Use when migrating Java applications that use local KeyStore, SSLContext, or Certificate classes to Azure Key Vault for secure certificate storage. +- **migration-confluent-cloud-kafka**: Migrates Java applications from self-hosted Apache Kafka to Apache Kafka on Confluent Cloud with passwordless authentication via Microsoft Entra ID. Updates Kafka connection configuration and authentication settings. Use when migrating Java Kafka producers or consumers to Confluent Cloud or enabling passwordless Entra ID authentication for Kafka on Confluent Cloud. +- **migration-cryptography-operations-to-azure-key-vault**: Migrates Java cryptographic operations (Cipher, Signature) from local key management to Azure Key Vault for centralized key management and cryptographic operations. Use when migrating Java applications that use javax.crypto.Cipher or java.security.Signature to Azure Key Vault, or centralizing cryptographic key management in Azure. +- **migration-eclipse-project-to-maven-project**: Migrates Java projects from Eclipse IDE project format (.project, .classpath) to Apache Maven project structure with pom.xml. Converts Eclipse build configuration and classpath settings to Maven conventions. Use when modernizing Eclipse-based Java projects to Maven, converting .project/.classpath files, or standardizing build tooling. +- **migration-ibm-db2-to-azure-postgresql**: Migrate IBM Db2 to Azure Database for PostgreSQL +- **migration-ibm-db2-to-azure-sql**: Migrate IBM Db2 to Azure SQL Database +- **migration-informix-to-postgresql**: Migrates Java application database layer from Informix Database to PostgreSQL, including JDBC driver changes, SQL syntax conversion, and Informix-specific feature replacement. Use when migrating Java applications from Informix to PostgreSQL, converting Informix SQL to PostgreSQL syntax, or replacing Informix JDBC drivers. +- **migration-java-ee-amqp-rabbitmq-servicebus**: Migrates Java EE/Jakarta EE applications from RabbitMQ AMQP messaging to Azure Service Bus SDK. Replaces RabbitMQ client dependencies, migrates publishers and consumers, updates connection management, and maps RabbitMQ concepts (exchanges, queues) to Service Bus equivalents (topics, subscriptions). Use when migrating Java EE or Jakarta EE applications from RabbitMQ to Azure Service Bus. +- **migration-javax.email-send-to-azure-communication-service-email**: Migrates Java applications from JavaMail (javax.mail) API to Azure Communication Service Email for sending emails. Replaces JavaMail email sending, message construction, and authentication with Azure Communication Service equivalents. Use when migrating Java applications from JavaMail or SMTP-based email sending to Azure Communication Service Email. +- **migration-jax-rpc-to-jax-ws**: Migrates Java web service implementations from deprecated JAX-RPC to JAX-WS. Updates web service configuration files and JAX-RPC specific code to use JAX-WS equivalents. Use when modernizing Java web services that use JAX-RPC or upgrading deprecated JAX-RPC APIs to the JAX-WS standard. +- **migration-kafka-to-eventhubs**: Migrates Java applications from Kafka to Azure Event Hubs for Kafka with managed identity for secure, passwordless authentication. Updates Spring Cloud Azure dependencies, Kafka connection settings, and authentication configuration. Use when migrating Java Kafka producers or consumers to Azure Event Hubs, or enabling managed identity authentication for event streaming. +- **migration-log-to-console**: Migrates Java application logging from file-based output to console-only output. Removes file appenders from logging configuration (logback, logging.xml) and ensures all log output goes to console. Use when containerizing Java applications, migrating to cloud-native logging, or preparing Java apps for Azure where console logging is preferred. +- **migration-mi-azure-sql**: Migrates Java Spring Boot projects from password-based authentication to Azure Managed Identity for connecting to Azure SQL Database. Updates Spring Cloud Azure dependencies and datasource configuration for passwordless authentication. Use when enabling managed identity for Azure SQL Database connections, removing hardcoded database passwords, or securing Java Spring Boot database authentication. +- **migration-mi-cassandra**: Migrates Java applications to connect to Azure Cosmos DB for Apache Cassandra using managed identity via Service Connector in Azure public cloud. Updates CqlSession configuration and Spring Data Cassandra properties. Use when migrating Java or Spring Boot applications to Azure Cosmos DB Cassandra API, enabling managed identity for Cassandra connections, or replacing Cassandra password authentication. +- **migration-mi-eventhub**: Migrates Java projects from password-based authentication to Azure Managed Identity for connecting to Azure Event Hubs. Adds Spring Cloud Azure dependencies and updates Event Hubs and Kafka configuration for passwordless authentication. Use when enabling managed identity for Azure Event Hubs in Java applications, removing Event Hubs connection string passwords, or securing event streaming authentication. +- **migration-mi-mariadb**: Migrates Java applications from password-based MariaDB authentication to Azure Managed Identity for Azure Database for MariaDB in public cloud using AzureMysqlAuthenticationPlugin. Updates JDBC connection and authentication configuration. Use when enabling managed identity for Azure Database for MariaDB, removing MariaDB passwords, or implementing credential-free MariaDB authentication in Azure. +- **migration-mi-mongodb**: Migrate from password-based authentication to Microsoft Entra ID authentication for Azure DocumentDB (with MongoDB Compatibility) in Java projects +- **migration-mi-mysql**: Migrates Java Spring Boot projects from password-based MySQL authentication to Azure Managed Identity for Azure Database for MySQL. Updates Spring Cloud Azure dependencies and datasource configuration for passwordless authentication. Use when enabling managed identity for Azure MySQL connections in Spring Boot, removing hardcoded MySQL passwords, or securing Spring datasource authentication. +- **migration-mi-postgresql**: Migrates Java Spring Boot projects from password-based PostgreSQL authentication to Azure Managed Identity for Azure Database for PostgreSQL. Updates Spring Cloud Azure dependencies and datasource configuration for passwordless authentication. Use when enabling managed identity for Azure PostgreSQL connections in Spring Boot, removing hardcoded PostgreSQL passwords, or securing Spring datasource authentication. +- **migration-mi-servicebus**: Azure Service Bus Managed Identity via Spring +- **migration-on-premises-user-authentication-to-microsoft-entra-id**: Migrates Java Spring Boot application user authentication from on-premises login to Microsoft Entra ID using spring-cloud-azure-starter-active-directory and spring-boot-starter-oauth2-client. Use when migrating Java web application authentication to Microsoft Entra ID, modernizing on-premises login to cloud identity, or adding Azure Active Directory authentication. +- **migration-oracle-to-postgresql**: Migrates Java application database layer from Oracle Database to PostgreSQL, including JDBC driver changes, SQL syntax conversion, and Oracle-specific feature replacement. Uses project-specific coding_notes.md guidance when available. Use when migrating Java applications from Oracle to PostgreSQL, converting Oracle SQL to PostgreSQL syntax, or replacing Oracle JDBC drivers. +- **migration-other-cache-solutions-to-azure-managed-cache**: Migrate other cache solutions to use Redis, and potentially to Azure Managed Redis / Azure Cache for Redis (retiring) while following best practices. Use this skill when users want to migrate other cache solutions to use Redis, such as Apache Commons JCS, DynaCache, Embedded cache, JCache, OSCache, ShiftOne, Oracle Coherence, etc., or local Redis to Azure Managed Redis / Azure Cache for Redis (retiring) with secure authentication changes. +- **migration-plaintext-credential-to-azure-keyvault**: Migrates hardcoded plaintext credentials (passwords, secrets, API keys, connection strings, tokens) in Java source code to Azure Key Vault for secure storage and retrieval. Use when securing Java applications by removing hardcoded credentials, migrating plaintext secrets to Azure Key Vault, or implementing centralized secret management. +- **migration-s3-to-azure-blob-storage**: Migrates Java applications from Amazon S3 SDK to Azure Blob Storage SDK, including bucket/container operations, object storage, access policies, and SAS token generation. Use when migrating Java applications from AWS S3 to Azure Blob Storage, replacing S3Client with BlobServiceClient, or converting AWS storage APIs to Azure equivalents. +- **migration-spring-jms-rabbitmq-servicebus**: Migrates Java Spring Boot applications from RabbitMQ JMS messaging to Azure Service Bus JMS. Replaces RabbitMQ JMS dependencies with Spring Cloud Azure Service Bus JMS starter and updates connection configuration. Use when migrating Spring JMS applications from RabbitMQ to Azure Service Bus, replacing RMQConnectionFactory, or modernizing JMS messaging to Azure. +- **migration-sqs-to-servicebus**: Migrates Java applications from AWS Simple Queue Service (SQS) to Azure Service Bus for message queuing. Replaces AWS SQS SDK dependencies with Azure Service Bus SDK, updates message sending and receiving code, and migrates queue configuration. Use when migrating Java applications from AWS SQS to Azure Service Bus, replacing SQS client code, or modernizing cloud message queuing to Azure. +- **migration-sybase-ase-to-azure-sql-database**: Migrates Java application database layer from Sybase ASE (Adaptive Server Enterprise) to Azure SQL Database with passwordless managed identity authentication. Use when migrating Java applications from Sybase ASE to Azure SQL. + +### Task Patterns without Skill Definitions +These patterns DO NOT have pre-defined skills. The pattern name and description define the modernization scenario, NOT A SKILL. They are in the format of `- **pattern-name**: pattern-description`. + +A pattern should be selected if it matches one of the customer's requirements, and there are no skills supporting this requirement. + +**IMPORTANT**: +- NEVER write the pattern name as skill name in the generated plan. +- Tasks generated from these patterns must have NO skill assigned. Do not reuse any skill from the "Task Patterns with Skill Definitions" section, even if a skill targets a similar technology or appears related. + +- **amazon-kinesis-to-azure-event-hubs**: Amazon Kinesis to Azure Event Hubs +- **amazon-sns-to-azure-service-bus**: Amazon SNS to Azure Service Bus +- **apache-pulsar-to-azure-event-hubs**: Apache Pulsar to Azure Event Hubs +- **aws-lambda-to-azure-functions**: AWS Lambda to Azure Functions +- **firebird-to-azure-postgresql**: Firebird to Azure PostgreSQL +- **google-cloud-bigtable-to-azure-cosmos-db**: Google Cloud Bigtable to Azure Cosmos DB +- **google-cloud-functions-to-azure-functions**: Google Cloud Functions to Azure Functions +- **google-cloud-pub-sub-to-azure-service-bus**: Google Cloud Pub/Sub to Azure Service Bus +- **google-cloud-spanner-to-azure-postgresql**: Google Cloud Spanner to Azure PostgreSQL +- **google-cloud-storage-to-azure-blob-storage**: Google Cloud Storage to Azure Blob Storage +- **google-firestore-to-azure-cosmos-db**: Google Firestore to Azure Cosmos DB +- **ibm-db2-to-azure-postgresql**: IBM DB2 to Azure PostgreSQL +- **ibm-mq-jms-to-azure-service-bus**: IBM MQ JMS to Azure Service Bus +- **migration-local-certificate-management-to-azure-key-vault**: Local certificate management to Azure Key Vault +- **migration-local-files-to-mounted-azure-storage**: Local files to mounted Azure Storage paths (starts with `${AZURE_MOUNT_PATH:/mnt/azure}`) +- **quartz-scheduler-to-azure-functions**: Quartz Scheduler to Azure Functions +- **solace-pubsub-to-azure-service-bus**: Solace PubSub+ to Azure Service Bus +- **spring-batch-to-azure-durable-functions**: Spring Batch to Azure Durable Functions +- **spring-cloud-config-to-azure-app-configuration**: Spring Cloud Config to Azure App Configuration +- **sqlite-to-azure-postgresql**: SQLite to Azure PostgreSQL +- **sybase-ase-to-azure-postgresql**: Sybase ASE to Azure Database for PostgreSQL +- **tibco-ems-jms-to-azure-service-bus**: TIBCO EMS JMS to Azure Service Bus diff --git a/.github/skills/create-modernization-plan/tasks-schema.json b/.github/skills/create-modernization-plan/tasks-schema.json new file mode 100644 index 000000000..397678e03 --- /dev/null +++ b/.github/skills/create-modernization-plan/tasks-schema.json @@ -0,0 +1,349 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Modernization Tasks Template", + "description": "Schema for tasks-template.json. Preserve field descriptions because they are used to generate the JSON by LLM.", + "type": "object", + "required": ["$schema", "description", "tasks", "metadata"], + "additionalProperties": false, + "properties": { + "$schema": { + "type": "string", + "description": "JSON Schema reference used by the template." + }, + "description": { + "type": "string", + "description": "Tasks template for modernization plan. Generate this file alongside plan.md to track individual migration tasks." + }, + "tasks": { + "type": "array", + "description": "List of individual migration tasks.", + "items": { + "oneOf": [ + { "$ref": "#/$defs/transformTask" }, + { "$ref": "#/$defs/upgradeTask" }, + { "$ref": "#/$defs/integrationTestTask" }, + { "$ref": "#/$defs/containerizationTask" }, + { "$ref": "#/$defs/deploymentTask" }, + { "$ref": "#/$defs/securityTask" }, + { "$ref": "#/$defs/infrastructureTask" } + ] + } + }, + "metadata": { + "type": "object", + "description": "Metadata for the plan.", + "additionalProperties": false, + "required": ["planName", "projectName", "language", "createdAt", "version"], + "properties": { + "planName": { + "type": "string", + "description": "[Plan name]" + }, + "projectName": { + "type": "string", + "description": "[Application Name]" + }, + "language": { + "type": "string", + "description": "[Programming language]" + }, + "createdAt": { + "type": "string", + "description": "[ISO 8601 timestamp]", + "format": "date-time" + }, + "version": { + "type": "string", + "description": "Version of the template schema.", + "const": "1.0" + } + } + } + }, + "$defs": { + "taskBase": { + "type": "object", + "additionalProperties": false, + "required": ["type", "id", "description", "requirements"], + "properties": { + "type": { + "type": "string", + "description": "Task type identifier." + }, + "id": { + "type": "string", + "description": "[Unique identifier for this task, start with sequence and category name, like '001-transform-migration-rabbitmq-to-servicebus']" + }, + "description": { + "type": "string", + "description": "[Brief description of what this task achieves from the user's perspective. It should describe high level requirements without dictating implementation details like API calls, package names, or code structure]" + }, + "reason": { + "type": "string", + "description": "[Explain why this task is needed, e.g., the motivation, business justification, or technical necessity that drives this task]" + }, + "requirements": { + "type": "string", + "description": "[The specific requirements for this task including: what to migrate (features, APIs, patterns), constraints (backward compatibility, modules to avoid), target Azure services, and technical preferences (authentication methods, patterns). It should describe high level requirements without dictating implementation details like API calls, package names, or code structure]" + }, + "environmentConfiguration": { + "type": ["string"], + "description": "[Environment configuration from user input (e.g. endpoint, access id). Omit this field if not specified]" + }, + "status": { + "type": ["string"], + "description": "Task execution status. Only has value after task is executed.", + "enum": ["pending", "started", "success", "failed", "skipped"] + }, + "taskSummary": { + "type": "string", + "description": "Summary of task execution result. Only has value after task is executed." + }, + "dependencies": { + "type": "array", + "description": "[List of task IDs that this task depends on. The task will only be executed after all its dependencies have completed successfully.]", + "items": { + "type": "string" + } + } + } + }, + "skill": { + "type": "object", + "additionalProperties": false, + "required": ["name", "location"], + "properties": { + "name": { + "type": "string", + "description": "[The skill name that will be used for this task, e.g., 'migration-rabbitmq-to-servicebus'.]" + }, + "location": { + "type": "string", + "description": "Skill location: project, remote and builtin, builtin is renamed from custom", + "enum": ["project","remote","builtin"] + } + } + }, + "successCriteria": { + "type": "object", + "additionalProperties": false, + "required": [ + "passBuild", + "generateNewUnitTests", + "passUnitTests" + ], + "description": "The task success criteria to validate after task execution.", + "properties": { + "passBuild": { + "type": ["string"], + "default": "true", + "description": "Project must compile successfully after migration, use default value if user does not specify" + }, + "generateNewUnitTests": { + "type": ["string"], + "default": "false", + "description": "Create mock-based unit tests for newly added Azure integration code to ensure test coverage, use default value if user does not specify" + }, + "passUnitTests": { + "type": ["string"], + "default": "true", + "description": "All unit tests must pass; mock dependent Azure resources if not provided, use default value if user does not specify" + } + } + }, + "successCriteriaStatus": { + "type": "object", + "not": { + "type": "null" + }, + "additionalProperties": false, + "description": "Optional validation status for each success criterion. Omit this field until validation is complete. If present, use true/false string values.", + "properties": { + "passBuild": { + "type": ["string"], + "description": "Validation status of passBuild criterion. true means passed, false means failed." + }, + "generateNewUnitTests": { + "type": ["string"], + "description": "Validation status of generateNewUnitTests criterion. true means passed, false means failed." + }, + "passUnitTests": { + "type": ["string"], + "description": "Validation status of passUnitTests criterion. true means passed, false means failed." + } + } + }, + "transformTask": { + "allOf": [ + { "$ref": "#/$defs/taskBase" }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "successCriteria"], + "properties": { + "type": { + "const": "transform", + "description": "transform task template" + }, + "skills": { + "type": "array", + "description": "The skills that will be used for this task, it is started with migration and looks like 'migration-rabbitmq-to-servicebus-mi'", + "items": { "$ref": "#/$defs/skill" } + }, + "successCriteria": { "$ref": "#/$defs/successCriteria" }, + "successCriteriaStatus": { "$ref": "#/$defs/successCriteriaStatus" } + } + } + ] + }, + "upgradeTask": { + "allOf": [ + { "$ref": "#/$defs/taskBase" }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "successCriteria"], + "properties": { + "type": { + "const": "upgrade", + "description": "Upgrade task template" + }, + "successCriteria": { "$ref": "#/$defs/successCriteria" }, + "successCriteriaStatus": { "$ref": "#/$defs/successCriteriaStatus" } + } + } + ] + }, + "containerizationTask": { + "allOf": [ + { "$ref": "#/$defs/taskBase" }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "dockerfilePath"], + "properties": { + "type": { + "const": "containerization", + "description": "Containerization task template - Only include if the target deployment requires containerization (e.g., AKS, ACA) or if the user explicitly requested containerization. Skip for non-containerized deployments." + }, + "dockerfilePath": { + "type": "string", + "description": "[Path to Dockerfile, indicate if existing or to be created]" + } + } + } + ] + }, + "deploymentTask": { + "allOf": [ + { "$ref": "#/$defs/taskBase" }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "targetAzureService", "resourceStatus", "deploymentTool", "skills"], + "properties": { + "type": { + "const": "deployment", + "description": "Deployment task template - Containerize, provision and deploy the application to Azure. Only include if the user explicitly requested deployment." + }, + "skills": { + "type": "array", + "description": "The deployment skill to use based on targetAzureService: 'azcli-aks-deploy' for Azure Kubernetes Service, 'azcli-containerapp-deploy' for Azure Container Apps, 'azcli-appservice-deploy' for Azure App Service, 'azcli-functionapp-deploy' for Azure Function App, 'azcli-appservicemi-deploy' for Azure App Service with Managed Identity", + "items": { "$ref": "#/$defs/skill" } + }, + "targetAzureService": { + "type": "string", + "description": "[Azure service name, e.g., Azure Container Apps, AKS, App Service]" + }, + "resourceStatus": { + "type": "string", + "description": "[Specify if using existing resource or will create new service]" + }, + "deploymentTool": { + "type": "string", + "description": "Infrastructure as Code tool type. Default to 'terraform' for aks, 'bicep' for other services if not specified.", + "enum": ["bicep", "terraform"] + } + } + } + ] + }, + "integrationTestTask": { + "allOf": [ + { "$ref": "#/$defs/taskBase" }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "layers"], + "properties": { + "type": { + "const": "integrationTest", + "description": "Integration test task template - Generate and run integration tests for migrated Azure services. Only include when user explicitly requests integration testing. This task runs after all transform/upgrade tasks but before containerization." + }, + "layers": { + "type": "array", + "description": "[Array of test layers to execute, e.g., [1, 2] for Layer 1 (Local Integration with TestContainers) and Layer 2 (Smoke Tests)]", + "items": { + "type": "number", + "enum": [1, 2] + }, + "minItems": 1 + } + } + } + ] + }, + "securityTask": { + "allOf": [ + { "$ref": "#/$defs/taskBase" }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "successCriteria"], + "properties": { + "type": { + "const": "security", + "description": "Security task template - Include when security remediation, vulnerability scanning, or compliance checks are required." + }, + "cveReport": { + "type": ["string"], + "description": "Path to final-cve-report.json. Only has value after CVE checking is complete." + }, + "successCriteria": { "$ref": "#/$defs/successCriteria" }, + "successCriteriaStatus": { "$ref": "#/$defs/successCriteriaStatus" } + } + } + ] + }, + "infrastructureTask": { + "allOf": [ + { "$ref": "#/$defs/taskBase" }, + { + "type": "object", + "additionalProperties": false, + "required": ["type", "skills", "iacType", "provision"], + "properties": { + "type": { + "const": "infrastructure", + "description": "Infrastructure task template - Generate IaC files (Bicep or Terraform) to provision Azure resources." + }, + "iacType": { + "type": "string", + "description": "Infrastructure as Code tool type.", + "enum": ["bicep", "terraform"] + }, + "provision": { + "type": "boolean", + "description": "Whether to provision Azure resources after generating IaC files." + }, + "skills": { + "type": "array", + "description": "The skill used for this task, e.g., 'infrastructure-bicep-generation' or 'infrastructure-terraform-generation'", + "items": { "$ref": "#/$defs/skill" } + } + } + } + ] + } + } +} diff --git a/.github/skills/cve-known-vulnerabilities/SKILL.md b/.github/skills/cve-known-vulnerabilities/SKILL.md new file mode 100644 index 000000000..2ab39f825 --- /dev/null +++ b/.github/skills/cve-known-vulnerabilities/SKILL.md @@ -0,0 +1,201 @@ +--- +name: cve-known-vulnerabilities +description: Detect known CVE vulnerabilities in Java project dependencies using the GitHub Security Advisories API +--- + +# CVE Security Assessment: Known Dependency Vulnerabilities + +## Role + +You are a **dependency security scanner**. Your task is to programmatically collect the project's Java dependencies, query the GitHub Security Advisories API for known CVEs, and report findings in a structured JSON format. + +> **Important:** You are an auditor, NOT an implementation developer. Your sole responsibility is to identify whether known CVE vulnerabilities exist in the project's dependencies. You may recommend dependency upgrades to patched versions, but do NOT make code changes or modify build files yourself. + +## Objective + +Identify all known CVE vulnerabilities in the project's direct and transitive dependencies by: +1. Detecting the build tool (Maven or Gradle) +2. Collecting dependency coordinates (`groupId:artifactId:version`) +3. Querying the GitHub Security Advisories API +4. Reporting findings in structured JSON format + +## Instructions + +### Step 1: Detect Build Tool + +Check which build tool the project uses: + +```bash +if [ -f "pom.xml" ]; then + BUILD_TOOL="maven" +elif [ -f "build.gradle" ] || [ -f "build.gradle.kts" ]; then + BUILD_TOOL="gradle" +else + echo "No supported Java build tool found" + mkdir -p security + printf '[]\n' > security/cve-assessment-result.json + exit 0 +fi +``` + +### Step 2: Collect Dependencies + +#### Maven Projects + +Try the CLI approach first (produces the most accurate results): + +```bash +# Detect wrapper vs bare command +if [ -f "./mvnw" ]; then MVN_CMD="./mvnw"; elif [ -f "./mvnw.cmd" ]; then MVN_CMD="./mvnw.cmd"; else MVN_CMD="mvn"; fi + +# Collect all dependencies as a tree (direct + transitive with parent chain) +$MVN_CMD dependency:tree -DoutputFile=/tmp/mvn-deps.txt -DappendOutput=true -B -q 2>/dev/null +``` + +Parse the output file. The tree format uses indentation to show the dependency hierarchy: +- The root line is the project itself (depth 0) — skip it. +- Depth-1 entries (prefixed with `+- ` or `\- `) are **direct** dependencies. +- Depth-2+ entries are **transitive** dependencies, nested under the direct dependency that pulls them in. +- Each entry has the format: `groupId:artifactId:packaging:version:scope` (5 parts) or `groupId:artifactId:packaging:classifier:version:scope` (6 parts). Extract `groupId:artifactId:version` from each. + +For transitive dependencies (depth 2+), trace them back to their depth-1 ancestor (the direct dependency that pulls them in), and use that direct dependency's declaration line number in the evidence. + +**Fallback** — if the CLI command fails or is unavailable, parse `pom.xml` files directly (direct dependencies only): +- Find all `pom.xml` files (excluding `target/`, `node_modules/`, `.git/`) +- Extract `` elements: ``, ``, `` +- Resolve `${property}` references from the `` section of the same POM +- Skip dependencies where the version cannot be resolved + +#### Gradle Projects + +Try the CLI approach first: + +```bash +# Detect wrapper vs bare command +if [ -f "./gradlew" ]; then GRADLE_CMD="./gradlew"; elif [ -f "./gradlew.bat" ]; then GRADLE_CMD="./gradlew.bat"; else GRADLE_CMD="gradle"; fi + +# In multi-project builds, Gradle automatically runs the task on all subprojects +$GRADLE_CMD dependencies --configuration runtimeClasspath -q 2>/dev/null +``` + +Parse the dependency tree output. The tree format uses indentation to show the dependency hierarchy: +- Depth-1 entries (top-level `+---` or `\---`) are **direct** dependencies. +- Depth-2+ entries (nested under a direct dependency) are **transitive** dependencies. + +For each dependency line: +- Only process lines containing `---` (tree markers like `+---` or `\---`) +- Strip tree formatting characters (`+---`, `\---`, `|`, leading whitespace) +- Handle version conflict resolution: `group:artifact:1.0 -> 2.0` means use version `2.0` +- Skip `project :submodule` entries and entries with `FAILED` versions +- Remove `(*)` duplicate markers and `(c)` constraint markers +- Extract `groupId:artifactId:version` + +For transitive dependencies (depth 2+), trace them back to their depth-1 ancestor (the direct dependency that pulls them in), and use that direct dependency's declaration line number in the evidence. + +**Fallback** — if the CLI command fails, parse `build.gradle` / `build.gradle.kts` files (direct dependencies only): +- Look for dependency declarations: `implementation`, `api`, `compileOnly`, `runtimeOnly`, etc. +- Extract GAV from patterns like `implementation 'group:artifact:version'` or `implementation("group:artifact:version")` +- Skip entries where the version contains `$` (unresolved properties) + +### Step 3: Query GitHub Security Advisories API + +Resolve the GitHub token and query the API: + +```bash +# Resolve token +TOKEN="${GITHUB_TOKEN:-$(gh auth token 2>/dev/null)}" +``` + +**IMPORTANT: Batch dependencies in groups of 30** to avoid URL length limits. + +For each batch, construct the `affects` parameter and query: + +```bash +# Build the affects parameter: groupId:artifactId@version,groupId:artifactId@version,... +# URL-encode the affects value + +# Query using gh api (preferred — avoids firewall issues): +gh api "/advisories?ecosystem=maven&affects=${ENCODED_AFFECTS}&per_page=100" \ + --header "X-GitHub-Api-Version: 2022-11-28" \ + --paginate +``` + +If `gh api` is not available, fall back to `curl`. Only include the `Authorization` header when a token is available; omit it for unauthenticated requests: + +```bash +AUTH_HEADER="" +if [ -n "$TOKEN" ]; then + AUTH_HEADER="-H \"Authorization: Bearer $TOKEN\"" +fi + +eval curl -s -H "Accept: application/vnd.github+json" \ + $AUTH_HEADER \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/advisories?ecosystem=maven&affects=${ENCODED_AFFECTS}&per_page=100" +``` + +**Handle pagination**: If using `curl`, check the `Link` response header for `rel="next"` and follow it until there are no more pages. + +### Step 4: Process API Results + +For each advisory returned: +1. **Skip** advisories where `withdrawn_at` is not null (withdrawn advisories) +2. **Extract** the CVE ID from `cve_id` (fall back to `ghsa_id` if `cve_id` is null) +3. **Extract** severity from the `severity` field +4. **Extract** `summary`, `description`, and `html_url` +5. **Extract** affected package details from `vulnerabilities[]`: `package.name`, `vulnerable_version_range`, `first_patched_version` + +### Step 5: Format and Locate Evidence + +For each CVE found: +1. Identify which build files (`pom.xml`, `build.gradle`, `build.gradle.kts`) declare the affected dependency +2. Find the line number where the dependency is declared (if possible) +3. Build the evidence with file paths (e.g., `pom.xml:42`) and a detailed explanation including: + - Link to the CVE advisory + - Severity level + - List of affected dependencies with their declaration locations + - Recommended fix (upgrade to patched version if available) + +## Output Format + +Write the result as a **flat JSON array** (NOT wrapped in a result envelope) to the output file. + +If **no vulnerabilities are found**, write an empty array: `[]` + +If **vulnerabilities are found**, write an array of finding objects: + +```json +[ + { + "id": "CVE-2024-22233", + "name": "Spring Framework URL parsing vulnerability", + "status": "FOUND", + "category": "CVE", + "severity": "high", + "storyPoint": 1, + "evidence": { + "files": ["pom.xml:42", "module-b/pom.xml:18"], + "explanation": "[CVE-2024-22233](https://github.com/advisories/GHSA-xxxx): Spring Framework URL parsing vulnerability\n\nSeverity: HIGH\n\nAffected dependencies:\n - org.springframework:spring-core:5.3.20 (declared at pom.xml:42)\n - io.netty:netty-codec-http:4.1.86 (transitive, pulled by spring-boot-starter-web at pom.xml:42)\n\nRecommended fix:\n - Upgrade org.springframework:spring-core to 5.3.27 or later" + } + } +] +``` + +### Field Requirements + +- **id**: The CVE identifier (e.g., `CVE-2024-22233`), or GHSA ID if no CVE ID exists +- **name**: The advisory summary text +- **status**: Always `"FOUND"` for reported vulnerabilities +- **category**: Always `"CVE"` +- **severity**: The raw GitHub Advisory severity: `"critical"`, `"high"`, `"medium"`, or `"low"` +- **storyPoint**: Always `1` +- **evidence.files**: Array of workspace-relative file paths where affected dependencies are declared (with optional `:lineNumber` suffix). For transitive dependencies, use the line number of the direct dependency that pulls them in (e.g., if `netty-codec-http` is pulled in by `spring-boot-starter-web` declared at `pom.xml:42`, use `pom.xml:42`) +- **evidence.explanation**: Markdown-formatted description including CVE link, severity, affected dependencies with locations, and recommended fix + +### Important Notes + +- If a single CVE affects multiple dependencies, group them into ONE finding with all affected dependencies listed in the explanation +- If no build tool is detected, write `[]` and skip the assessment +- If the GitHub token is unavailable, proceed anyway (unauthenticated rate limits apply) +- If the API query fails, write `[]` and log the error +- Deduplicate: if the same CVE appears across multiple API batches, merge into a single finding diff --git a/.github/skills/cwe-code-quality/SKILL.md b/.github/skills/cwe-code-quality/SKILL.md new file mode 100644 index 000000000..9785ba5a3 --- /dev/null +++ b/.github/skills/cwe-code-quality/SKILL.md @@ -0,0 +1,153 @@ +--- +name: cwe-code-quality +description: Assess codebase for CWE code quality vulnerabilities (CWE-130, CWE-456, CWE-457, CWE-477, CWE-570, CWE-571, CWE-606, CWE-665, CWE-681, CWE-682, CWE-772, CWE-775, CWE-783, CWE-789, CWE-835, CWE-1057) +--- + +# CWE Security Assessment: Code Quality + +## Role + +You are an expert **code security reviewer** specializing in CWE vulnerability detection. + +> **Important:** You are an auditor, NOT an implementation developer. Your sole responsibility is to identify whether the target vulnerabilities exist in the codebase. Do NOT suggest fixes or improvements. + +## Objective + +Analyze the application codebase for each of the 16 CWE rules listed below. For each rule, determine whether the vulnerability pattern exists in the codebase. + +## CWE Rules to Assess + +### CWE-130: Improper Handling of Length Parameter Inconsistency +- **Severity:** potential | **Story Points:** 3 +- **Description:** The product parses a formatted message or structure, but it does not handle or incorrectly handles a length field that is inconsistent with the actual length of the associated data. + +### CWE-456: Missing Initialization of a Variable +- **Severity:** potential | **Story Points:** 2 +- **Description:** The product does not initialize critical variables, which causes the execution environment to use unexpected values. + +### CWE-457: Use of Uninitialized Variable +- **Severity:** potential | **Story Points:** 2 +- **Description:** The code uses a variable that has not been initialized, leading to unpredictable or unintended results. + +### CWE-477: Use of Obsolete Function +- **Severity:** optional | **Story Points:** 1 +- **Description:** The code uses deprecated or obsolete functions, which suggests that the code has not been actively reviewed or maintained. + +### CWE-570: Expression is Always False +- **Severity:** optional | **Story Points:** 1 +- **Description:** The product contains an expression that will always evaluate to false. + +### CWE-571: Expression is Always True +- **Severity:** optional | **Story Points:** 1 +- **Description:** The product contains an expression that will always evaluate to true. + +### CWE-606: Unchecked Input for Loop Condition +- **Severity:** potential | **Story Points:** 3 +- **Description:** The product does not properly check inputs that are used for loop conditions, potentially leading to a denial of service or other consequences because of excessive looping. + +### CWE-665: Improper Initialization +- **Severity:** potential | **Story Points:** 3 +- **Description:** The product does not initialize or incorrectly initializes a resource, which might leave the resource in an unexpected state when it is accessed or used. + +### CWE-681: Incorrect Conversion between Numeric Types +- **Severity:** potential | **Story Points:** 3 +- **Description:** When converting from one data type to another, such as long to integer, data can be omitted or translated in a way that produces unexpected values. If the resulting values are used in a sensitive context, then dangerous behaviors may occur. + +### CWE-682: Incorrect Calculation +- **Severity:** potential | **Story Points:** 5 +- **Description:** The product performs a calculation that generates incorrect or unintended results that are later used in security-critical decisions or resource management. + +### CWE-772: Missing Release of Resource after Effective Lifetime +- **Severity:** potential | **Story Points:** 3 +- **Description:** The product does not release a resource after its effective lifetime has ended, i.e., after the resource is no longer needed. + +### CWE-775: Missing Release of File Descriptor or Handle after Effective Lifetime +- **Severity:** potential | **Story Points:** 3 +- **Description:** The product does not release a file descriptor or handle after its effective lifetime has ended, i.e., after the file descriptor/handle is no longer needed. + +### CWE-783: Operator Precedence Logic Error +- **Severity:** optional | **Story Points:** 1 +- **Description:** The product uses an expression in which operator precedence causes incorrect logic to be used. + +### CWE-789: Memory Allocation with Excessive Size Value +- **Severity:** potential | **Story Points:** 5 +- **Description:** The product allocates memory based on an untrusted, large size value, but it does not ensure that the size is within expected limits, allowing arbitrary amounts of memory to be allocated. + +### CWE-835: Loop with Unreachable Exit Condition ('Infinite Loop') +- **Severity:** potential | **Story Points:** 3 +- **Description:** The product contains an iteration or loop with an exit condition that cannot be reached, i.e., an infinite loop. + +### CWE-1057: Data Access Operations Outside of Expected Data Manager Component +- **Severity:** potential | **Story Points:** 5 +- **Description:** The product uses a dedicated, central data manager component as required by design, but it contains code that performs data-access operations that do not use this data manager. + +## Instructions + +1. **Iterate through each CWE rule** listed above +2. **Systematically scan** the application source code for patterns matching each rule +3. **For each rule:** Stop scanning as soon as you find the FIRST confirmed match +4. **Continue to the next rule** after finding a match or exhausting the search +5. **Report findings** for ALL rules (both FOUND and NOT_FOUND) + +### Search Strategy + +- Start with common vulnerability patterns: user input handling, external data processing, resource management +- Check configuration files, API endpoints, data access layers, and utility classes +- Consider both direct patterns and indirect/transitive vulnerability paths +- Focus on source files (e.g., `.java`, `.py`, `.cs`, `.js`, `.ts`) — skip test files and generated code + +## Output Format + +Use the `write_assessment_result` tool to save results with the following JSON structure: + +```json +{ + "input_name": "CWE - Code Quality", + "analysis_method": "LLM", + "status": "success", + "result": { + "finding": "Assessed 16 CWE rules in Code Quality: X FOUND, Y NOT_FOUND", + "confidence": "high", + "evidence": ["Scanned application source files for code quality patterns"], + "values": [ + { + "id": "", + "name": "", + "status": "FOUND", + "category": "Code Quality", + "severity": "", + "storyPoint": "", + "description": "", + "evidence": { + "files": ["src/path/to/File.java"], + "explanation": "Description of the vulnerability found, including class/method and line reference" + } + }, + { + "id": "", + "name": "", + "status": "NOT_FOUND", + "category": "Code Quality", + "severity": "", + "storyPoint": "", + "description": "", + "evidence": { + "files": [], + "explanation": "" + } + } + ] + }, + "execution_time_seconds": 0, + "timestamp": "" +} +``` + +### Evidence Rules + +- **FOUND**: `files` must contain workspace-relative file paths. `explanation` must describe the vulnerability with class, method, and/or line references. +- **NOT_FOUND**: `files` must be an empty array `[]`. `explanation` must be an empty string `""`. +- **Every rule** listed above MUST have exactly one entry in `values` — do NOT skip any rule. +- **For every entry** (both FOUND and NOT_FOUND), copy the `severity`, `storyPoint`, and `description` values exactly as documented in the corresponding rule definition in the "CWE Rules to Assess" section above. +- Set top-level `status` to `"not_applicable"` ONLY if the entire category is irrelevant to the project's language/technology stack. +- Update the `finding` summary with actual counts of FOUND and NOT_FOUND rules. diff --git a/.github/skills/cwe-concurrency-synchronization/SKILL.md b/.github/skills/cwe-concurrency-synchronization/SKILL.md new file mode 100644 index 000000000..7ff74ae9e --- /dev/null +++ b/.github/skills/cwe-concurrency-synchronization/SKILL.md @@ -0,0 +1,113 @@ +--- +name: cwe-concurrency-synchronization +description: Assess codebase for CWE concurrency & synchronization vulnerabilities (CWE-543, CWE-567, CWE-662, CWE-667, CWE-820, CWE-821) +--- + +# CWE Security Assessment: Concurrency & Synchronization + +## Role + +You are an expert **code security reviewer** specializing in CWE vulnerability detection. + +> **Important:** You are an auditor, NOT an implementation developer. Your sole responsibility is to identify whether the target vulnerabilities exist in the codebase. Do NOT suggest fixes or improvements. + +## Objective + +Analyze the application codebase for each of the 6 CWE rules listed below. For each rule, determine whether the vulnerability pattern exists in the codebase. + +## CWE Rules to Assess + +### CWE-543: Use of Singleton Pattern Without Synchronization in a Multithreaded Context +- **Severity:** potential | **Story Points:** 5 +- **Description:** The product uses the singleton pattern when creating a resource within a multithreaded environment. + +### CWE-567: Unsynchronized Access to Shared Data in a Multithreaded Context +- **Severity:** potential | **Story Points:** 5 +- **Description:** The product does not properly synchronize shared data, such as static variables across threads, which can lead to undefined behavior and unpredictable data changes. + +### CWE-662: Improper Synchronization +- **Severity:** potential | **Story Points:** 8 +- **Description:** The product utilizes multiple threads or processes to allow temporary access to a shared resource that can only be exclusive to one process at a time, but it does not properly synchronize these actions, which might cause simultaneous accesses of this resource by multiple threads or processes. + +### CWE-667: Improper Locking +- **Severity:** potential | **Story Points:** 8 +- **Description:** The product does not properly acquire or release a lock on a resource, leading to unexpected resource state changes and behaviors. + +### CWE-820: Missing Synchronization +- **Severity:** potential | **Story Points:** 8 +- **Description:** The product utilizes a shared resource in a concurrent manner but does not attempt to synchronize access to the resource. + +### CWE-821: Incorrect Synchronization +- **Severity:** potential | **Story Points:** 8 +- **Description:** The product utilizes a shared resource in a concurrent manner, but it does not correctly synchronize access to the resource. + +## Instructions + +1. **Iterate through each CWE rule** listed above +2. **Systematically scan** the application source code for patterns matching each rule +3. **For each rule:** Stop scanning as soon as you find the FIRST confirmed match +4. **Continue to the next rule** after finding a match or exhausting the search +5. **Report findings** for ALL rules (both FOUND and NOT_FOUND) + +### Search Strategy + +- Start with common vulnerability patterns: user input handling, external data processing, resource management +- Check configuration files, API endpoints, data access layers, and utility classes +- Consider both direct patterns and indirect/transitive vulnerability paths +- Focus on source files (e.g., `.java`, `.py`, `.cs`, `.js`, `.ts`) — skip test files and generated code + +## Output Format + +Use the `write_assessment_result` tool to save results with the following JSON structure: + +```json +{ + "input_name": "CWE - Concurrency & Synchronization", + "analysis_method": "LLM", + "status": "success", + "result": { + "finding": "Assessed 6 CWE rules in Concurrency & Synchronization: X FOUND, Y NOT_FOUND", + "confidence": "high", + "evidence": ["Scanned application source files for concurrency & synchronization patterns"], + "values": [ + { + "id": "", + "name": "", + "status": "FOUND", + "category": "Concurrency & Synchronization", + "severity": "", + "storyPoint": "", + "description": "", + "evidence": { + "files": ["src/path/to/File.java"], + "explanation": "Description of the vulnerability found, including class/method and line reference" + } + }, + { + "id": "", + "name": "", + "status": "NOT_FOUND", + "category": "Concurrency & Synchronization", + "severity": "", + "storyPoint": "", + "description": "", + "evidence": { + "files": [], + "explanation": "" + } + } + ] + }, + "execution_time_seconds": 0, + "timestamp": "" +} +``` + +### Evidence Rules + +- **FOUND**: `files` must contain workspace-relative file paths. `explanation` must describe the vulnerability with class, method, and/or line references. +- **NOT_FOUND**: `files` must be an empty array `[]`. `explanation` must be an empty string `""`. +- **Every rule** listed above MUST have exactly one entry in `values` — do NOT skip any rule. +- **For every entry** (both FOUND and NOT_FOUND), copy the `severity`, `storyPoint`, and `description` values exactly as documented in the corresponding rule definition in the "CWE Rules to Assess" section above. +- Set top-level `status` to `"not_applicable"` ONLY if the entire category is irrelevant to the project's language/technology stack. +- Update the `finding` summary with actual counts of FOUND and NOT_FOUND rules. diff --git a/.github/skills/cwe-credentials-secrets/SKILL.md b/.github/skills/cwe-credentials-secrets/SKILL.md new file mode 100644 index 000000000..fe1e29b6f --- /dev/null +++ b/.github/skills/cwe-credentials-secrets/SKILL.md @@ -0,0 +1,109 @@ +--- +name: cwe-credentials-secrets +description: Assess codebase for CWE credentials & secrets vulnerabilities (CWE-259, CWE-321, CWE-732, CWE-778, CWE-798) +--- + +# CWE Security Assessment: Credentials & Secrets + +## Role + +You are an expert **code security reviewer** specializing in CWE vulnerability detection. + +> **Important:** You are an auditor, NOT an implementation developer. Your sole responsibility is to identify whether the target vulnerabilities exist in the codebase. Do NOT suggest fixes or improvements. + +## Objective + +Analyze the application codebase for each of the 5 CWE rules listed below. For each rule, determine whether the vulnerability pattern exists in the codebase. + +## CWE Rules to Assess + +### CWE-259: Use of Hard-coded Password +- **Severity:** optional | **Story Points:** 5 +- **Description:** The product contains a hard-coded password, which it uses for its own inbound authentication or for outbound communication to external components. + +### CWE-321: Use of Hard-coded Cryptographic Key +- **Severity:** potential | **Story Points:** 5 +- **Description:** The product uses a hard-coded, unchangeable cryptographic key. + +### CWE-732: Incorrect Permission Assignment for Critical Resource +- **Severity:** optional | **Story Points:** 5 +- **Description:** The product specifies permissions for a security-critical resource in a way that allows that resource to be read or modified by unintended actors. + +### CWE-778: Insufficient Logging +- **Severity:** potential | **Story Points:** 3 +- **Description:** When a security-critical event occurs, the product either does not record the event or omits important details about the event when logging it. + +### CWE-798: Use of Hard-coded Credentials +- **Severity:** optional | **Story Points:** 5 +- **Description:** The product contains hard-coded credentials, such as a password or cryptographic key. + +## Instructions + +1. **Iterate through each CWE rule** listed above +2. **Systematically scan** the application source code for patterns matching each rule +3. **For each rule:** Stop scanning as soon as you find the FIRST confirmed match +4. **Continue to the next rule** after finding a match or exhausting the search +5. **Report findings** for ALL rules (both FOUND and NOT_FOUND) + +### Search Strategy + +- Start with common vulnerability patterns: user input handling, external data processing, resource management +- Check configuration files, API endpoints, data access layers, and utility classes +- Consider both direct patterns and indirect/transitive vulnerability paths +- Focus on source files (e.g., `.java`, `.py`, `.cs`, `.js`, `.ts`) — skip test files and generated code + +## Output Format + +Use the `write_assessment_result` tool to save results with the following JSON structure: + +```json +{ + "input_name": "CWE - Credentials & Secrets", + "analysis_method": "LLM", + "status": "success", + "result": { + "finding": "Assessed 5 CWE rules in Credentials & Secrets: X FOUND, Y NOT_FOUND", + "confidence": "high", + "evidence": ["Scanned application source files for credentials & secrets patterns"], + "values": [ + { + "id": "", + "name": "", + "status": "FOUND", + "category": "Credentials & Secrets", + "severity": "", + "storyPoint": "", + "description": "", + "evidence": { + "files": ["src/path/to/File.java"], + "explanation": "Description of the vulnerability found, including class/method and line reference" + } + }, + { + "id": "", + "name": "", + "status": "NOT_FOUND", + "category": "Credentials & Secrets", + "severity": "", + "storyPoint": "", + "description": "", + "evidence": { + "files": [], + "explanation": "" + } + } + ] + }, + "execution_time_seconds": 0, + "timestamp": "" +} +``` + +### Evidence Rules + +- **FOUND**: `files` must contain workspace-relative file paths. `explanation` must describe the vulnerability with class, method, and/or line references. +- **NOT_FOUND**: `files` must be an empty array `[]`. `explanation` must be an empty string `""`. +- **Every rule** listed above MUST have exactly one entry in `values` — do NOT skip any rule. +- **For every entry** (both FOUND and NOT_FOUND), copy the `severity`, `storyPoint`, and `description` values exactly as documented in the corresponding rule definition in the "CWE Rules to Assess" section above. +- Set top-level `status` to `"not_applicable"` ONLY if the entire category is irrelevant to the project's language/technology stack. +- Update the `finding` summary with actual counts of FOUND and NOT_FOUND rules. diff --git a/.github/skills/cwe-file-path-security/SKILL.md b/.github/skills/cwe-file-path-security/SKILL.md new file mode 100644 index 000000000..1cac17b05 --- /dev/null +++ b/.github/skills/cwe-file-path-security/SKILL.md @@ -0,0 +1,109 @@ +--- +name: cwe-file-path-security +description: Assess codebase for CWE file & path security vulnerabilities (CWE-22, CWE-23, CWE-36, CWE-434, CWE-611) +--- + +# CWE Security Assessment: File & Path Security + +## Role + +You are an expert **code security reviewer** specializing in CWE vulnerability detection. + +> **Important:** You are an auditor, NOT an implementation developer. Your sole responsibility is to identify whether the target vulnerabilities exist in the codebase. Do NOT suggest fixes or improvements. + +## Objective + +Analyze the application codebase for each of the 5 CWE rules listed below. For each rule, determine whether the vulnerability pattern exists in the codebase. + +## CWE Rules to Assess + +### CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') +- **Severity:** optional | **Story Points:** 8 +- **Description:** The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory. + +### CWE-23: Relative Path Traversal +- **Severity:** optional | **Story Points:** 5 +- **Description:** The product uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize sequences such as .. that can resolve to a location that is outside of that directory. + +### CWE-36: Absolute Path Traversal +- **Severity:** optional | **Story Points:** 5 +- **Description:** The product uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize absolute path sequences such as /abs/path that can resolve to a location that is outside of that directory. + +### CWE-434: Unrestricted Upload of File with Dangerous Type +- **Severity:** mandatory | **Story Points:** 8 +- **Description:** The product allows the upload or transfer of dangerous file types that are automatically processed within its environment. + +### CWE-611: Improper Restriction of XML External Entity Reference +- **Severity:** optional | **Story Points:** 5 +- **Description:** The product processes an XML document that can contain XML entities with URIs that resolve to documents outside of the intended sphere of control, causing the product to embed incorrect documents into its output. + +## Instructions + +1. **Iterate through each CWE rule** listed above +2. **Systematically scan** the application source code for patterns matching each rule +3. **For each rule:** Stop scanning as soon as you find the FIRST confirmed match +4. **Continue to the next rule** after finding a match or exhausting the search +5. **Report findings** for ALL rules (both FOUND and NOT_FOUND) + +### Search Strategy + +- Start with common vulnerability patterns: user input handling, external data processing, resource management +- Check configuration files, API endpoints, data access layers, and utility classes +- Consider both direct patterns and indirect/transitive vulnerability paths +- Focus on source files (e.g., `.java`, `.py`, `.cs`, `.js`, `.ts`) — skip test files and generated code + +## Output Format + +Use the `write_assessment_result` tool to save results with the following JSON structure: + +```json +{ + "input_name": "CWE - File & Path Security", + "analysis_method": "LLM", + "status": "success", + "result": { + "finding": "Assessed 5 CWE rules in File & Path Security: X FOUND, Y NOT_FOUND", + "confidence": "high", + "evidence": ["Scanned application source files for file & path security patterns"], + "values": [ + { + "id": "", + "name": "", + "status": "FOUND", + "category": "File & Path Security", + "severity": "", + "storyPoint": "", + "description": "", + "evidence": { + "files": ["src/path/to/File.java"], + "explanation": "Description of the vulnerability found, including class/method and line reference" + } + }, + { + "id": "", + "name": "", + "status": "NOT_FOUND", + "category": "File & Path Security", + "severity": "", + "storyPoint": "", + "description": "", + "evidence": { + "files": [], + "explanation": "" + } + } + ] + }, + "execution_time_seconds": 0, + "timestamp": "" +} +``` + +### Evidence Rules + +- **FOUND**: `files` must contain workspace-relative file paths. `explanation` must describe the vulnerability with class, method, and/or line references. +- **NOT_FOUND**: `files` must be an empty array `[]`. `explanation` must be an empty string `""`. +- **Every rule** listed above MUST have exactly one entry in `values` — do NOT skip any rule. +- **For every entry** (both FOUND and NOT_FOUND), copy the `severity`, `storyPoint`, and `description` values exactly as documented in the corresponding rule definition in the "CWE Rules to Assess" section above. +- Set top-level `status` to `"not_applicable"` ONLY if the entire category is irrelevant to the project's language/technology stack. +- Update the `finding` summary with actual counts of FOUND and NOT_FOUND rules. diff --git a/.github/skills/cwe-injection-attacks/SKILL.md b/.github/skills/cwe-injection-attacks/SKILL.md new file mode 100644 index 000000000..347105abb --- /dev/null +++ b/.github/skills/cwe-injection-attacks/SKILL.md @@ -0,0 +1,137 @@ +--- +name: cwe-injection-attacks +description: Assess codebase for CWE injection attacks vulnerabilities (CWE-77, CWE-78, CWE-79, CWE-88, CWE-89, CWE-90, CWE-91, CWE-99, CWE-502, CWE-564, CWE-643, CWE-652) +--- + +# CWE Security Assessment: Injection Attacks + +## Role + +You are an expert **code security reviewer** specializing in CWE vulnerability detection. + +> **Important:** You are an auditor, NOT an implementation developer. Your sole responsibility is to identify whether the target vulnerabilities exist in the codebase. Do NOT suggest fixes or improvements. + +## Objective + +Analyze the application codebase for each of the 12 CWE rules listed below. For each rule, determine whether the vulnerability pattern exists in the codebase. + +## CWE Rules to Assess + +### CWE-77: Improper Neutralization of Special Elements used in a Command ('Command Injection') +- **Severity:** mandatory | **Story Points:** 13 +- **Description:** The product constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component. + +### CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') +- **Severity:** mandatory | **Story Points:** 13 +- **Description:** The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component. + +### CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') +- **Severity:** optional | **Story Points:** 8 +- **Description:** The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users. + +### CWE-88: Improper Neutralization of Argument Delimiters in a Command ('Argument Injection') +- **Severity:** optional | **Story Points:** 5 +- **Description:** The product constructs a string for a command to be executed by a separate component in another control sphere, but it does not properly delimit the intended arguments, options, or switches within that command string. + +### CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') +- **Severity:** mandatory | **Story Points:** 13 +- **Description:** The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data. + +### CWE-90: Improper Neutralization of Special Elements used in an LDAP Query ('LDAP Injection') +- **Severity:** optional | **Story Points:** 5 +- **Description:** The product constructs all or part of an LDAP query using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended LDAP query when it is sent to a downstream component. + +### CWE-91: XML Injection (aka Blind XPath Injection) +- **Severity:** optional | **Story Points:** 5 +- **Description:** The product does not properly neutralize special elements that are used in XML, allowing attackers to modify the syntax, content, or commands of the XML before it is processed by an end system. + +### CWE-99: Improper Control of Resource Identifiers ('Resource Injection') +- **Severity:** potential | **Story Points:** 3 +- **Description:** The product receives input from an upstream component, but it does not restrict or incorrectly restricts the input before it is used as an identifier for a resource that may be outside the intended sphere of control. + +### CWE-502: Deserialization of Untrusted Data +- **Severity:** mandatory | **Story Points:** 13 +- **Description:** The product deserializes untrusted data without sufficiently ensuring that the resulting data will be valid. + +### CWE-564: SQL Injection: Hibernate +- **Severity:** mandatory | **Story Points:** 8 +- **Description:** Using Hibernate to execute a dynamic SQL statement built with user-controlled input can allow an attacker to modify the statement's meaning or to execute arbitrary SQL commands. + +### CWE-643: Improper Neutralization of Data within XPath Expressions ('XPath Injection') +- **Severity:** optional | **Story Points:** 5 +- **Description:** The product uses external input to dynamically construct an XPath expression used to retrieve data from an XML database, but it does not neutralize or incorrectly neutralizes that input. This allows an attacker to control the structure of the query. + +### CWE-652: Improper Neutralization of Data within XQuery Expressions ('XQuery Injection') +- **Severity:** optional | **Story Points:** 5 +- **Description:** The product uses external input to dynamically construct an XQuery expression used to retrieve data from an XML database, but it does not neutralize or incorrectly neutralizes that input. This allows an attacker to control the structure of the query. + +## Instructions + +1. **Iterate through each CWE rule** listed above +2. **Systematically scan** the application source code for patterns matching each rule +3. **For each rule:** Stop scanning as soon as you find the FIRST confirmed match +4. **Continue to the next rule** after finding a match or exhausting the search +5. **Report findings** for ALL rules (both FOUND and NOT_FOUND) + +### Search Strategy + +- Start with common vulnerability patterns: user input handling, external data processing, resource management +- Check configuration files, API endpoints, data access layers, and utility classes +- Consider both direct patterns and indirect/transitive vulnerability paths +- Focus on source files (e.g., `.java`, `.py`, `.cs`, `.js`, `.ts`) — skip test files and generated code + +## Output Format + +Use the `write_assessment_result` tool to save results with the following JSON structure: + +```json +{ + "input_name": "CWE - Injection Attacks", + "analysis_method": "LLM", + "status": "success", + "result": { + "finding": "Assessed 12 CWE rules in Injection Attacks: X FOUND, Y NOT_FOUND", + "confidence": "high", + "evidence": ["Scanned application source files for injection attacks patterns"], + "values": [ + { + "id": "", + "name": "", + "status": "FOUND", + "category": "Injection Attacks", + "severity": "", + "storyPoint": "", + "description": "", + "evidence": { + "files": ["src/path/to/File.java"], + "explanation": "Description of the vulnerability found, including class/method and line reference" + } + }, + { + "id": "", + "name": "", + "status": "NOT_FOUND", + "category": "Injection Attacks", + "severity": "", + "storyPoint": "", + "description": "", + "evidence": { + "files": [], + "explanation": "" + } + } + ] + }, + "execution_time_seconds": 0, + "timestamp": "" +} +``` + +### Evidence Rules + +- **FOUND**: `files` must contain workspace-relative file paths. `explanation` must describe the vulnerability with class, method, and/or line references. +- **NOT_FOUND**: `files` must be an empty array `[]`. `explanation` must be an empty string `""`. +- **Every rule** listed above MUST have exactly one entry in `values` — do NOT skip any rule. +- **For every entry** (both FOUND and NOT_FOUND), copy the `severity`, `storyPoint`, and `description` values exactly as documented in the corresponding rule definition in the "CWE Rules to Assess" section above. +- Set top-level `status` to `"not_applicable"` ONLY if the entire category is irrelevant to the project's language/technology stack. +- Update the `finding` summary with actual counts of FOUND and NOT_FOUND rules. diff --git a/.github/skills/cwe-memory-safety/SKILL.md b/.github/skills/cwe-memory-safety/SKILL.md new file mode 100644 index 000000000..be7ffa439 --- /dev/null +++ b/.github/skills/cwe-memory-safety/SKILL.md @@ -0,0 +1,149 @@ +--- +name: cwe-memory-safety +description: Assess codebase for CWE memory safety vulnerabilities (CWE-119, CWE-120, CWE-123, CWE-125, CWE-415, CWE-416, CWE-672, CWE-786, CWE-787, CWE-788, CWE-805, CWE-822, CWE-823, CWE-824, CWE-825) +--- + +# CWE Security Assessment: Memory Safety + +## Role + +You are an expert **code security reviewer** specializing in CWE vulnerability detection. + +> **Important:** You are an auditor, NOT an implementation developer. Your sole responsibility is to identify whether the target vulnerabilities exist in the codebase. Do NOT suggest fixes or improvements. + +## Objective + +Analyze the application codebase for each of the 15 CWE rules listed below. For each rule, determine whether the vulnerability pattern exists in the codebase. + +## CWE Rules to Assess + +### CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer +- **Severity:** mandatory | **Story Points:** 21 +- **Description:** The product performs operations on a memory buffer, but it reads from or writes to a memory location outside the buffer's intended boundary. This may result in read or write operations on unexpected memory locations that could be linked to other variables, data structures, or internal program data. + +### CWE-120: Buffer Copy without Checking Size of Input ('Classic Buffer Overflow') +- **Severity:** mandatory | **Story Points:** 13 +- **Description:** The product copies an input buffer to an output buffer without verifying that the size of the input buffer is less than the size of the output buffer. + +### CWE-123: Write-what-where Condition +- **Severity:** mandatory | **Story Points:** 13 +- **Description:** Any condition where the attacker has the ability to write an arbitrary value to an arbitrary location, often as the result of a buffer overflow. + +### CWE-125: Out-of-bounds Read +- **Severity:** optional | **Story Points:** 8 +- **Description:** The product reads data past the end, or before the beginning, of the intended buffer. + +### CWE-415: Double Free +- **Severity:** optional | **Story Points:** 8 +- **Description:** The product calls free() twice on the same memory address. + +### CWE-416: Use After Free +- **Severity:** mandatory | **Story Points:** 13 +- **Description:** The product reuses or references memory after it has been freed. At some point afterward, the memory may be allocated again and saved in another pointer, while the original pointer references a location somewhere within the new allocation. Any operations using the original pointer are no longer valid because the memory belongs to the code that operates on the new pointer. + +### CWE-672: Operation on a Resource after Expiration or Release +- **Severity:** potential | **Story Points:** 5 +- **Description:** The product uses, accesses, or otherwise operates on a resource after that resource has been expired, released, or revoked. + +### CWE-786: Access of Memory Location Before Start of Buffer +- **Severity:** optional | **Story Points:** 8 +- **Description:** The product reads or writes to a buffer using an index or pointer that references a memory location prior to the beginning of the buffer. + +### CWE-787: Out-of-bounds Write +- **Severity:** mandatory | **Story Points:** 13 +- **Description:** The product writes data past the end, or before the beginning, of the intended buffer. + +### CWE-788: Access of Memory Location After End of Buffer +- **Severity:** optional | **Story Points:** 8 +- **Description:** The product reads or writes to a buffer using an index or pointer that references a memory location after the end of the buffer. + +### CWE-805: Buffer Access with Incorrect Length Value +- **Severity:** optional | **Story Points:** 8 +- **Description:** The product uses a sequential operation to read or write a buffer, but it uses an incorrect length value that causes it to access memory that is outside of the bounds of the buffer. + +### CWE-822: Untrusted Pointer Dereference +- **Severity:** optional | **Story Points:** 8 +- **Description:** The product obtains a value from an untrusted source, converts this value to a pointer, and dereferences the resulting pointer. + +### CWE-823: Use of Out-of-range Pointer Offset +- **Severity:** optional | **Story Points:** 8 +- **Description:** The product performs pointer arithmetic on a valid pointer, but it uses an offset that can point outside of the intended range of valid memory locations for the resulting pointer. + +### CWE-824: Access of Uninitialized Pointer +- **Severity:** optional | **Story Points:** 5 +- **Description:** The product accesses or uses a pointer that has not been initialized. + +### CWE-825: Expired Pointer Dereference +- **Severity:** optional | **Story Points:** 8 +- **Description:** The product dereferences a pointer that contains a location for memory that was previously valid, but is no longer valid. + +## Instructions + +1. **Iterate through each CWE rule** listed above +2. **Systematically scan** the application source code for patterns matching each rule +3. **For each rule:** Stop scanning as soon as you find the FIRST confirmed match +4. **Continue to the next rule** after finding a match or exhausting the search +5. **Report findings** for ALL rules (both FOUND and NOT_FOUND) + +### Search Strategy + +- Start with common vulnerability patterns: user input handling, external data processing, resource management +- Check configuration files, API endpoints, data access layers, and utility classes +- Consider both direct patterns and indirect/transitive vulnerability paths +- Focus on source files (e.g., `.java`, `.py`, `.cs`, `.js`, `.ts`) — skip test files and generated code + +## Output Format + +Use the `write_assessment_result` tool to save results with the following JSON structure: + +```json +{ + "input_name": "CWE - Memory Safety", + "analysis_method": "LLM", + "status": "success", + "result": { + "finding": "Assessed 15 CWE rules in Memory Safety: X FOUND, Y NOT_FOUND", + "confidence": "high", + "evidence": ["Scanned application source files for memory safety patterns"], + "values": [ + { + "id": "", + "name": "", + "status": "FOUND", + "category": "Memory Safety", + "severity": "", + "storyPoint": "", + "description": "", + "evidence": { + "files": ["src/path/to/File.java"], + "explanation": "Description of the vulnerability found, including class/method and line reference" + } + }, + { + "id": "", + "name": "", + "status": "NOT_FOUND", + "category": "Memory Safety", + "severity": "", + "storyPoint": "", + "description": "", + "evidence": { + "files": [], + "explanation": "" + } + } + ] + }, + "execution_time_seconds": 0, + "timestamp": "" +} +``` + +### Evidence Rules + +- **FOUND**: `files` must contain workspace-relative file paths. `explanation` must describe the vulnerability with class, method, and/or line references. +- **NOT_FOUND**: `files` must be an empty array `[]`. `explanation` must be an empty string `""`. +- **Every rule** listed above MUST have exactly one entry in `values` — do NOT skip any rule. +- **For every entry** (both FOUND and NOT_FOUND), copy the `severity`, `storyPoint`, and `description` values exactly as documented in the corresponding rule definition in the "CWE Rules to Assess" section above. +- Set top-level `status` to `"not_applicable"` ONLY if the entire category is irrelevant to the project's language/technology stack. +- Update the `finding` summary with actual counts of FOUND and NOT_FOUND rules. diff --git a/.github/skills/data-architecture/SKILL.md b/.github/skills/data-architecture/SKILL.md new file mode 100644 index 000000000..4dd3a43d6 --- /dev/null +++ b/.github/skills/data-architecture/SKILL.md @@ -0,0 +1,227 @@ +--- +name: data-architecture +description: Generate data architecture and persistence layer documentation with data model diagram +--- + +# Data Architecture & Persistence Layer + +Analyze the project to document database configuration, entity models, data ownership boundaries, repository interfaces, and caching strategies. Generate a Mermaid ER diagram showing entity relationships. Save to `.github/modernize/assessment/engines/data-architecture.md`. + +## Input Parameters + +- `workspace-path` (optional): Path to the project to analyze (defaults to current directory) + +## Scope Boundaries — Avoid Redundancy with Other Skills + +This skill is part of a set of four complementary assessment skills. To avoid content duplication across their output documents, observe these scope rules: + +- **Introduction**: Write a 1-2 sentence intro focused on the data layer (number of entities, database types, ORM). Do NOT restate the application's overall architecture type, web framework, or API surface — those are covered by other skills. +- **Configuration property keys/values** (e.g., `spring.jpa.hibernate.ddl-auto`, `spring.sql.init.*`) are owned by the `configuration-inventory` skill. In the Database Configuration table, describe the *behavior* (e.g., "Hibernate does not manage schema; SQL scripts are authoritative") but do NOT list raw property key-value pairs. Reference `configuration-inventory.md` for the full property inventory. +- **API endpoints and HTTP methods** are owned by the `api-service-contracts` skill. Do NOT list controller endpoints or HTTP paths. Repository methods are in scope for this skill; controller routes are not. +- **Business workflow steps and validation rules** are owned by the `business-workflows` skill. Do NOT describe multi-step business processes or enumerate validation constraints. When documenting entity relationships (cascade, fetch), focus on the persistence/ORM implications, not the business process flow. +- **Deployment configurations** (Docker Compose, K8s, profiles) are owned by the `configuration-inventory` skill. Mention database profiles only in the Database Configuration table to identify which DB is used per profile — do NOT describe Docker Compose services, K8s manifests, or deployment targets in detail. + +## Execution Steps + +### Step 1: Generate Database Configuration Section + +Extract database configuration from project files, **per profile/environment**, and produce the complete `## Database Configuration` section: + +- Database types: HSQLDB, MySQL, PostgreSQL, MongoDB, SQL Server, Oracle, SQLite, CosmosDB, DynamoDB +- **Per-profile configuration**: identify which database is used in each profile (e.g., HSQLDB for `default`/dev in-memory testing, MySQL for `production`/`mysql` profile) +- Database drivers per profile (e.g., `mysql-connector-java` for production, `hsqldb` for development) +- Connection configuration: connection strings, JDBC URLs, pooling settings (HikariCP, connection pool size) +- Migration tools: Flyway, Liquibase, EF Migrations, Alembic, Prisma Migrate, Knex migrations +- Schema management: DDL auto-generation settings (`spring.jpa.hibernate.ddl-auto`), schema versioning, initial schema scripts +- Seed data: `data.sql`, `import.sql`, seed migration files, or programmatic data seeding + +### Step 2: Generate Data Ownership per Service Section + +Determine table/entity ownership across modules/services and produce the complete `## Data Ownership per Service` section. Scope is strictly the per-service ownership table — high-level data boundary discussion belongs in Step 6. + +For each module/service, identify: + +- Which tables/entities it owns (bounded context analysis) +- ORM framework used (e.g., Hibernate, EF Core, MyBatis, Mongoose) +- Caching layer used by this service (if any) +- Brief notes (e.g., outbox table, schema-per-service) + +> Do NOT include shared-vs-isolated data store summary, cross-service data access patterns, or read/write/CQRS observations here — those belong in the `## Data Ownership Boundaries` section (Step 6). + +### Step 3: Generate Entity Model Section + +Scan source code for data access patterns and ORM entities, then produce the complete `## Entity Model` section: + +**Analysis:** +- Java: JPA/Hibernate entities (`@Entity`, `@Table`), Spring Data repositories (`JpaRepository`, `CrudRepository`), MyBatis mappers, JDBC templates +- .NET: EF Core `DbContext`, EF Core entities, Dapper, ADO.NET +- JavaScript/TypeScript: Mongoose models/schemas, Sequelize models, TypeORM entities, Prisma schema, Knex migrations + +Identify: +- Entity/model classes with their fields, types, and constraints — note the source file path for each entity +- Transaction management annotations/configuration (`@Transactional`, `TransactionScope`, etc.) +- Bidirectional vs unidirectional relationship mappings (e.g., `owner.addPet(pet)` establishing parent-child links) + +**Diagram — Mermaid `erDiagram`:** +- Show primary entities with key fields (PK, FK) +- Use standard cardinality notation: `||--o{` (one-to-many), `||--||` (one-to-one), `}o--o{` (many-to-many) +- Group related entities logically +- Include relationship labels +- Annotate which service owns each entity group (use comments or subgraph labels) + +Example: + +~~~mermaid +erDiagram + Owner ||--o{ Pet : "has" + Pet ||--o{ Visit : "has" + Pet }o--|| PetType : "is of" + Vet }o--o{ Specialty : "has" + Owner { + int id PK + string firstName + string lastName + string address + string city + string telephone + } + Pet { + int id PK + string name + date birthDate + int ownerId FK + int typeId FK + } + PetType { + int id PK + string name + } + Visit { + int id PK + int petId FK + date visitDate + string description + } + Vet { + int id PK + string firstName + string lastName + } + Specialty { + int id PK + string name + } +~~~ + +### Step 4: Generate Key Repository Methods Section + +For each service/module, document the key repository interfaces and produce the complete `## Key Repository Methods` section: + +- Repository interface name, entity type, and source file path +- Standard CRUD methods inherited from base interface +- Custom query methods with their signatures and purposes — especially: + - Bulk/batch queries (e.g., `findByPetIdIn(Collection)`) used for cross-service aggregation + - Custom finders with derived query methods + - Named queries or `@Query`-annotated methods + - Raw SQL or stored procedure calls +- Query method parameters and return types + +### Step 5: Generate Caching Strategy Section + +Identify caching layers and configuration and produce the complete `## Caching Strategy` section: + +- Cache providers: EhCache, Redis, Caffeine, Spring Cache (`@Cacheable`, `@CacheEvict`), MemoryCache, IDistributedCache +- Cache configuration: TTL, eviction policies, cache regions/names +- Cache-aside, read-through, write-through, write-behind patterns +- Session caching, query result caching, second-level cache (Hibernate) +- Rationale for caching decisions (e.g., "veterinarian data is read frequently but changes rarely") +- JSR-107 (JCache) / `cache-api` usage and provider binding + +### Step 6: Generate Data Ownership Boundaries Section + +Document data-store topology and cross-service access semantics, plus data classification, then produce the complete `## Data Ownership Boundaries` section (including the `### Data Classification & Sensitivity` subsection): + +**Boundaries:** +- Shared vs isolated data stores (shared database, database-per-service, logical separation within shared DB) +- Cross-service data access patterns: how one service queries another service's data (direct DB access vs REST API calls vs batch/bulk query methods such as `findByPetIdIn(...)` that enable gateway-level aggregation) +- Read/write patterns and CQRS observations across services + +**Data Classification & Sensitivity (`### Data Classification & Sensitivity` subsection):** +- Identify whether stored data contains sensitive categories — PII (names, addresses, phone numbers, emails), PHI (health records), PCI (payment card data) +- For each sensitive category found, note whether encryption-at-rest, data masking, or field-level access controls are in place +- If absent, state this explicitly (e.g., "Owner entity stores PII (firstName, lastName, address, telephone); no encryption-at-rest or masking configured") + +### Step 7: Save Output + +Save to `.github/modernize/assessment/engines/data-architecture.md` with this exact structure: + +``` +# Data Architecture & Persistence Layer + +A brief introduction (1-2 sentences) summarizing the data layer. + +## Database Configuration + +[Table: Service/Module | DB Type | Profile | Driver | Connection | Migration Tool] + +## Data Ownership per Service + +[Table: Service | Tables Owned | ORM Framework | Caching | Notes] + +## Entity Model + +< Mermaid erDiagram here > + +## Key Repository Methods + +[Table: Service | Repository | Notable Methods | Purpose] + +## Caching Strategy + +[Table or description of caching layers, providers, TTL, patterns, and rationale] + +## Data Ownership Boundaries + +[Description of shared vs isolated data stores, cross-service data access patterns, and aggregation enablers] + +### Data Classification & Sensitivity + +[Table: Entity | Sensitive Fields | Classification (PII/PHI/PCI/None) | Controls in Place] +[If no sensitive data found: "No PII, PHI, or PCI data detected in entity model."] +``` + +## Scaling Rules + +- If the project has **more than 30 entities**, aggregate minor entities and show only the core domain model (15-20 key entities) +- Keep the ER diagram under **40 entities** to ensure readability and GitHub rendering compatibility +- For multi-module projects, focus on inter-module entity relationships and data boundaries +- Collapse join tables into relationship annotations rather than showing them as separate entities +- In the repository methods table, focus on non-CRUD custom methods; omit standard inherited methods + +## Mermaid Syntax Rules + +- Use `erDiagram` +- Avoid special characters (`@`, `#`, `$`, `%`, `&`) in entity/field names — use plain text +- Use standard cardinality: `||--o{`, `||--||`, `}o--o{`, `|o--o{` +- Use quoted relationship labels: `Owner ||--o{ Pet : "has"` +- Do not use backticks inside entity or field names +- **Never put `{` or `}` inside a quoted attribute description or relationship label.** Mermaid's ER parser treats `{` as the entity-body opener even inside quotes, which crashes the entire diagram with "Syntax error in text". Avoid placeholder syntax like `{userId}`, `{path}`, or `{id}` in attribute comments — write `userId`, `path`, `id` (or describe in plain words: "indexed by user id"). Example: + - ❌ `string Key PK "Redis key /basket/{BuyerId}"` + - ✅ `string Key PK "Redis key /basket/"` or `"Redis key prefixed with /basket/"` + +## Error Handling + +- **Unsupported project type**: Output a single line: `> ERROR: Unsupported project type. This skill supports Java, .NET, JavaScript, and TypeScript projects only.` +- **No data access layer found**: Output: `> ERROR: No recognized data access patterns or entities found at {workspace-path}. Verify the path is correct.` +- **Insufficient info**: Generate a best-effort diagram from available data. Add a note: `> Note: Some entities or relationships could not be fully identified.` + +## Success Criteria + +- Database configuration table lists all discovered databases with type, profile, driver, and migration tools +- Data ownership table maps each service to its owned tables, ORM, and caching layer +- Mermaid ER diagram renders correctly showing entity relationships with cardinality and key fields +- Repository methods table documents custom query methods with purposes, especially cross-service aggregation enablers +- Caching strategy section describes cache providers, patterns, and rationale +- Data ownership boundaries describe shared vs isolated stores and cross-service data access patterns +- Data Classification & Sensitivity table identifies PII/PHI/PCI fields and documents presence or absence of controls +- File saved to `.github/modernize/assessment/engines/data-architecture.md` diff --git a/.github/skills/dependency-map/SKILL.md b/.github/skills/dependency-map/SKILL.md new file mode 100644 index 000000000..60a929e78 --- /dev/null +++ b/.github/skills/dependency-map/SKILL.md @@ -0,0 +1,180 @@ +--- +name: dependency-map +description: Generate dependency map diagram from project build files +--- + +# Dependency Map + +Analyze project build and package files to generate a visual map of all external dependencies grouped by functional category. Save to `.github/modernize/assessment/engines/dependency-map.md`. + +This skill focuses exclusively on **declared external dependencies** (libraries, frameworks, packages). For internal application structure and component relationships, see the `architecture-diagram` skill. + +## Input Parameters + +- `workspace-path` (optional): Path to the project to analyze (defaults to current directory) + +## Execution Steps + +### Step 1: Generate Dependencies Section + +Analyze build files and produce the complete `## Dependencies` section in one pass: + +**Analysis — examine only build and package management files** (do NOT scan source code — that is the `architecture-diagram` skill's job): + +- Java: pom.xml, build.gradle, settings.gradle, gradle.properties, gradle lockfiles +- .NET: *.csproj, Directory.Build.props, packages.config, Directory.Packages.props +- JavaScript/TypeScript: package.json, package-lock.json, yarn.lock, pnpm-lock.yaml + +For each dependency extract: +- Group/package name and artifact name +- Declared version (or version range) +- Scope (compile, runtime, test, provided) + +Also detect: +- Parent POM / BOM imports (Java) +- Central package management (.NET Directory.Packages.props) +- Transitive dependencies where visible from lock files or BOM + +**Categorize** dependencies into functional groups: + +| Category | Examples | +|----------|----------| +| Web Frameworks | Spring Web, ASP.NET Core MVC, JAX-RS | +| Database / ORM | Hibernate, Entity Framework, JDBC drivers | +| Messaging | Kafka client, RabbitMQ, Azure Service Bus | +| Caching | Redis, EhCache, MemoryCache | +| Logging | SLF4J, Log4j, Serilog, NLog | +| Security | Spring Security, Microsoft.Identity, OAuth libs | +| Observability | Micrometer, OpenTelemetry, Application Insights | +| Utilities | Guava, Apache Commons, Lombok, AutoMapper | + +Rules: +- Exclude test-scoped dependencies (JUnit, xUnit, Mockito, etc.) from the main diagram and Dependency Summary table — they are not relevant for modernization planning +- If a dependency doesn't fit any category, put it under "Utilities" +- Collect test-scoped dependencies separately for the Test Dependencies section (Step 2) + +**Diagram — Mermaid `flowchart LR`:** +- Application as the central left-side node +- One `subgraph` per functional category +- Each dependency as a node showing name and version: `Lib["Library Name v1.2.3"]` +- Arrows from Application to each category subgraph +- If a BOM/parent POM manages versions, show it as a separate node linked to the dependencies it governs + +Example: + +~~~mermaid +flowchart LR + App["MyApplication"] + + subgraph Web["Web Frameworks"] + SpringMVC["Spring MVC 5.3.x"] + Thymeleaf["Thymeleaf 3.0"] + end + subgraph DB["Database / ORM"] + Hibernate["Hibernate 5.6"] + PgDriver["PostgreSQL Driver 42.6"] + end + subgraph Messaging + Kafka["Kafka Client 3.4"] + end + subgraph Cache["Caching"] + Redis["Jedis 4.3"] + end + subgraph Log["Logging"] + SLF4J["SLF4J 1.7"] + Logback["Logback 1.2"] + end + subgraph Sec["Security"] + SpringSec["Spring Security 5.7"] + end + subgraph Util["Utilities"] + Lombok["Lombok 1.18"] + Jackson["Jackson 2.14"] + end + + App -->|"web"| Web + App -->|"persistence"| DB + App -->|"messaging"| Messaging + App -->|"caching"| Cache + App -->|"logging"| Log + App -->|"security"| Sec + App -->|"utilities"| Util + SLF4J -.->|"implementation"| Logback +~~~ + +**Textual explanations (write immediately after the diagram):** +- **Dependency Summary table**: Category | Count | Key Libraries | Notes (e.g., Web Frameworks | 2 | ASP.NET MVC 5.2.7, Razor 3.2.7 | Legacy MVC stack on .NET Framework) +- **Version & Compatibility Risks**: A short paragraph highlighting dependencies that are outdated, end-of-life, or have known migration concerns (e.g., ".NET Framework 4.7.2 is in maintenance mode; Entity Framework 6 has a migration path to EF Core") +- **Notable Observations**: 2-4 bullet points on anything noteworthy — duplicate functionality across libraries, deprecated packages, security-sensitive dependencies, or unusually large transitive trees + +### Step 2: Generate Test Dependencies Section + +Collect all test-scoped dependencies (excluded from the main diagram) and produce the complete `## Test Dependencies` section: + +- List detected test frameworks and supporting libraries with their versions (e.g., JUnit 5, Mockito, AssertJ, Testcontainers, xUnit, Jest) +- Report the total number of test-scope dependencies +- Note any test infrastructure concerns (e.g., outdated test framework version, missing contract-testing library, no integration test framework detected) + +### Step 3: Save Output + +Save to `.github/modernize/assessment/engines/dependency-map.md` with this exact structure: + +``` +# Dependency Map + +A brief introduction (1-2 sentences) stating project name and total dependency count. + +## Dependencies + +< Mermaid flowchart LR here > + +### Dependency Summary + +[Table: Category | Count | Key Libraries | Notes] + +### Version & Compatibility Risks + +[Short paragraph on outdated or end-of-life dependencies] + +### Notable Observations + +[2-4 bullet points on noteworthy findings] + +## Test Dependencies + +[Table: Framework | Version | Notes] + +Total test-scope dependencies: N +[1-2 sentences on test infrastructure observations, or "No test dependencies detected."] +``` + +## Scaling Rules + +- If the project has **more than 50 declared dependencies**, collapse minor utilities into a single aggregate node (e.g., `Utils["12 utility libraries"]`) and only show individually the top dependencies by importance +- Keep the diagram under **40 nodes** to ensure readability and GitHub rendering compatibility +- For multi-module projects (e.g., multi-module Maven/Gradle, multi-project .sln), show shared dependencies once and module-specific dependencies grouped by module + +## Mermaid Syntax Rules + +- Use `flowchart LR` +- Avoid special characters (`@`, `#`, `$`, `%`, `&`) in node labels — use plain text +- Always quote arrow labels with double quotes: `-->|"label"|` +- Use `subgraph` for grouping, with a display name in quotes if it contains spaces +- Use `-.->` (dotted arrow) for transitive/indirect relationships +- Verify all node IDs are unique across the entire diagram + +## Error Handling + +- **Unsupported project type**: Output a single line: `> ERROR: Unsupported project type. This skill supports Java, .NET, JavaScript, and TypeScript projects only.` +- **No build files found**: Output: `> ERROR: No recognized build files found at {workspace-path}. Verify the path is correct.` +- **Incomplete dependency info**: Generate a best-effort diagram from available data. Add a note inside the diagram: `Note["Some dependencies could not be fully resolved"]` + +## Success Criteria + +- Mermaid diagram renders correctly with dependencies grouped by functional category +- Each dependency shows name and version +- Dependency Summary table lists categories with counts and key libraries +- Version & Compatibility Risks paragraph highlights outdated or end-of-life dependencies +- Notable Observations lists 2-4 noteworthy findings +- Test Dependencies section lists detected test frameworks with versions and total count +- File saved to `.github/modernize/assessment/engines/dependency-map.md` diff --git a/.github/skills/dotnet-upgrade-assessment/SKILL.md b/.github/skills/dotnet-upgrade-assessment/SKILL.md new file mode 100644 index 000000000..6e6e6aba7 --- /dev/null +++ b/.github/skills/dotnet-upgrade-assessment/SKILL.md @@ -0,0 +1,149 @@ +--- +name: dotnet-upgrade-assessment +description: Runs .NET upgrade assessment only. Does not create plans, execute upgrades, or modify source code. +--- + +# .NET Upgrade Assessment Skill + +This skill runs a .NET upgrade assessment using the MCP server. It performs ONLY assessment — no plans, no code changes, no upgrades. + +## Input Parameters + +- `workspace-path` (optional): Path to the project root containing the `.sln` or `.slnx` file. Defaults to `.` (current directory). For a repository with multiple solutions, pass the solution folder path. +- `target-framework` (optional): Target .NET framework version for upgrade (e.g., `net8`, `net9`, `net10`). + +## Prerequisites — MCP Tool Availability Check + +Before doing anything else, verify that ALL of the following MCP tools are available: +- `get_state` +- `get_scenarios` +- `initialize_scenario` +- `generate_dotnet_upgrade_assessment` + +**If ANY of these tools are NOT available, STOP immediately.** Do NOT attempt to perform the assessment manually, do NOT analyze code yourself, and do NOT use alternative approaches. Simply report that the required MCP tools are unavailable and exit. + +## Prerequisites — Locate the Report Directory + +Before calling any MCP tools, find the versioned report directory that was created by the core assessment skill. The directory is at: + +``` +{workspace-path}/.github/modernize/assessment/reports/report-{reportId}/ +``` + +Where `reportId` is a 14-digit timestamp in `yyyyMMddHHmmss` format (e.g., `20250514065424`). Find the latest one: + +```bash +REPORT_DIR=$(ls -d {workspace-path}/.github/modernize/assessment/reports/report-[0-9]* 2>/dev/null | sort | tail -1) +``` + +If `REPORT_DIR` is empty (no `report-*` directory exists), report an error and STOP — the core assessment must run first. + +## Prerequisites — ua-settings.json Setup + +Create the settings file pointing to the discovered report directory: + +```bash +mkdir -p {workspace-path}/.github/modernize +cat > {workspace-path}/.github/modernize/ua-settings.json << 'EOF' +{ + "outputPath": "{REPORT_DIR}" +} +EOF +export UA_SETTINGS_FILE_PATH="$(realpath {workspace-path}/.github/modernize/ua-settings.json)" +``` + +Replace `{workspace-path}` with the actual parameter value and `{REPORT_DIR}` with the discovered report directory path from the previous step. + +## Execution Steps + +Execute these steps IN ORDER. Do NOT skip any step. Do NOT deviate from this sequence. + +### Step 1: Call `get_state()` + +Call the MCP tool `get_state()` to initialize the workflow state. + +### Step 2: Call `get_scenarios()` + +Call the MCP tool `get_scenarios()`. From the response, identify the scenario for ".NET version upgrade" and note its `scenarioId`. + +### Step 3: Call `initialize_scenario` + +Call the MCP tool `initialize_scenario` with: +- `scenarioId`: the ID found in Step 2 +- `description`: "Upgrade assessment for {solution file name}" + +### Step 4: Find the solution file or folder + +Look for `.sln` or `.slnx` files in `{workspace-path}`. Use the first one found. +If no solution file exists, use projects mode with `{workspace-path}` directly. + +### Step 5: Call `generate_dotnet_upgrade_assessment` + +Call `generate_dotnet_upgrade_assessment` on the selected solution, project, folder, or project list. Scope the assessment to the user's selected projects when the request is not solution-wide. +If `target-framework` was provided as an input parameter, pass it as `targetFramework` in the request as well. + +For a solution-wide assessment: + +```json +{ + "inputMode": "solution", + "paths": "{absolute-path-to-solution}", + "targetFramework": "net10.0" +} +``` + +For scoped project assessments, pass one or more absolute project paths as a semicolon-delimited string: + +```json +{ + "inputMode": "projects", + "paths": "{absolute-path-to-project-1};{absolute-path-to-project-2}", + "targetFramework": "net10.0" +} +``` + +For folder assessments, pass the absolute folder path: + +```json +{ + "inputMode": "folder", + "paths": "{absolute-path-to-folder}", + "targetFramework": "net10.0" +} +``` + + + +### Step 6: Verify scenario output files + +Confirm that the MCP server wrote assessment files under `{REPORT_DIR}/scenarios/dotnet-version-upgrade/`. Expected files include: +- `assessment.json` +- `assessment.csv` +- `assessment.md` +- `scenario.json` +- `dependencies-health.json` + +If the directory or files are missing, report what was generated and note the discrepancy. + +### Step 7: Return the result + +Report the assessment output file path (`{REPORT_DIR}/scenarios/dotnet-version-upgrade/`). STOP. + +## Cleanup + +After the assessment completes (whether successful or failed), remove the settings file: + +```bash +rm -f {workspace-path}/.github/modernize/ua-settings.json +``` + +## Rules + +- **ALL work MUST go through MCP tool calls.** Do NOT analyze code yourself. +- **Do NOT create plans or modify source files.** This is assessment only. +- **Do NOT ask for user input.** Run all steps in one session without pausing. +- **If a tool call fails, report the exact error.** Do NOT work around it or retry with different parameters. +- **Do NOT load additional skills or instructions.** Follow only the steps above. +- **Do NOT call `get_instructions`, `start_task`, `complete_task`, `break_down_task`, or any task/workflow management tools.** Those are for upgrade execution, not assessment. +- **Do NOT create git branches, commits, or source control operations.** +- **Do NOT read or analyze source code files.** The MCP tools handle all analysis internally. diff --git a/.github/skills/execute-java-upgrade-task/SKILL.md b/.github/skills/execute-java-upgrade-task/SKILL.md new file mode 100644 index 000000000..d6957ae37 --- /dev/null +++ b/.github/skills/execute-java-upgrade-task/SKILL.md @@ -0,0 +1,431 @@ +--- +name: execute-java-upgrade-task +description: Execute a Java upgrade task as part of a modernization plan +--- + +You are an expert Java upgrade agent. **Task**: Upgrade to user-specified target versions by (1) generating an incremental plan and (2) executing it per the rules below. + +You MUST generate the upgrade plan and execute it by yourself following the rules and workflow. You are now in the "modernize-java" agent. You MUST NOT call `#generate-upgrade-plan` or `#redirect-to-upgrade-agent` again as it will redirect to you, causing an infinite loop. + +## Rules + +### Upgrade Success Criteria (ALL must be met) + +- **Goal**: All user-specified target versions met. +- **Compilation**: Both main source code AND test code compile successfully = `mvn clean test-compile` (or equivalent) succeeds. This includes compiling production code and all test classes. +- **Test**: **100% test pass rate** = `mvn clean test` succeeds. Minimum acceptable: test pass rate ≥ baseline (pre-upgrade pass rate). Every test failure MUST be fixed unless proven to be a pre-existing flaky test (documented with evidence from baseline run). **Skip if user set "Run tests before and after the upgrade: false" in plan.md Options.** + +### Anti-Excuse Rules (MANDATORY) + +- **NO premature termination**: Token limits, time constraints, or complexity are NEVER valid reasons to skip fixing test failures. +- **NO "close enough" acceptance**: 95% is NOT 100%. Every failing test requires a fix attempt with documented root cause. +- **NO deferred fixes**: "Fix post-merge", "TODO later", "can be addressed separately" are NOT acceptable. Fix NOW or document as a genuine unfixable limitation with exhaustive justification. +- **NO categorical dismissals**: "Test-specific issues", "doesn't affect production", "sample/demo code", "non-blocking" are NOT valid reasons to skip fixes. ALL tests must pass. +- **NO blame-shifting**: "Known framework issue", "migration behavior change", "infrastructure problem" require YOU to implement the fix or workaround, not document and move on. +- **Genuine limitations ONLY**: A limitation is valid ONLY if: (1) multiple distinct fix approaches were attempted and documented, (2) root cause is clearly identified, (3) fix is technically impossible without breaking other functionality. + +### Review Code Changes (MANDATORY for each step) + +After completing changes in each step, review code changes per the rules in `progress.md` templates BEFORE verification. Key areas: + +- **Sufficiency**: all required upgrade changes are present +- **Necessity**: no CRITICAL unnecessary changes — Unnecessary changes that do not affect behavior may be retained; however, it is essential to ensure that the functional behavior remains consistent and security controls are preserved. + +### Upgrade Strategy + +- **Incremental upgrades**: Stepwise dependency upgrades; use intermediates to avoid large jumps breaking builds. +- **Minimal changes**: Only upgrade dependencies essential for compatibility with target versions. +- **Risk-first**: Handle EOL/challenging deps early in isolated steps. +- **Necessary/Meaningful steps only**: Each step MUST change code/config. NO steps for pure analysis/validation. Merge small related changes. **Test**: "Does this step modify project files?" +- **Automation tools**: Use automation tools like OpenRewrite etc. for efficiency; always verify output. +- **Successor preference**: Compatible successor > Adapter pattern > Code rewrite. +- **Build tool compatibility**: Check Maven/Gradle version compatibility with the target JDK. Upgrade the build tool (including wrapper) if the current version does not support the target JDK. Common minimum versions: Maven 3.9+ / Gradle 8.5+ for Java 21, Maven 4.0+ / Gradle 9.1+ for Java 25. When a wrapper (`mvnw`/`gradlew`) is present, also upgrade the wrapper-defined version in `.mvn/wrapper/maven-wrapper.properties` or `gradle/wrapper/gradle-wrapper.properties`. +- **Temporary errors OK**: Steps may pass with known errors if resolved later or pre-existing. +- **CVE version pin protection**: Before removing or downgrading any explicit `` override in `` or ``, verify it is not a CVE-driven pin. Check for: (1) nearby XML comments referencing CVE IDs or security fixes, (2) whether the pinned version is **newer** than the BOM-managed version — if so, it likely exists to patch a vulnerability. When in doubt, **keep the override** and document the decision. + +### Execution Guidelines + +- **Wrapper preference**: Use Maven Wrapper (`mvnw`/`mvnw.cmd`) or Gradle Wrapper (`gradlew`/`gradlew.bat`) when present in the project root, unless user explicitly specifies otherwise. This ensures consistent build tool versions across environments. +- **Version control via tool**: 🛑 NEVER use direct `git` commands in terminal — ONLY use `#version-control` for ALL version control operations (check status, create branch, commit, stash, discard changes). **ALWAYS pass `sessionId: `** to every `#version-control` call for telemetry tracking. When `GIT_AVAILABLE=false` (git not installed or project is not a git repository), skip ALL version control operations. Files remain uncommitted in the working directory. Use `N/A` for `` and `` placeholders. Record a notice in `plan.md` that changes are not version-controlled during this upgrade. +- **Version control timing**: `#version-control` requires `SESSION_ID` which is only available after Phase 1 (Precheck) succeeds. Do NOT use `#version-control` during Precheck. Git availability detection is deferred to Phase 2 Initialize. +- **Template compliance**: For `plan.md`, follow the **Plan Format Specification** below and write the complete file in a **single `create_file` call** — do NOT read a template or use `insert_edit_into_file` during plan generation. For `progress.md`, follow the rules and samples in each section's HTML comments of the template file. For `summary.md`, read `summary.template.md` (in the session directory) as a spec, then write `summary.md` as a new file using `create_file`. +- **Uninterrupted run**: Complete each phase fully without pausing for user input, except for the mandatory user confirmation after plan generation (Phase 3). +- **User input**: Prefer `#askQuestions` tool when available to collect user input (e.g., choices, confirmations). Fall back to plain-text prompts only when `#askQuestions` is unavailable. + +### Event Reporting (MANDATORY) + +Call `#report-event` immediately at each key milestone. **NO skipping. NO batching. This is non-negotiable.** + +- **When**: Report at every milestone defined in the Workflow phases — do not wait until the end of a phase. +- **Details**: Pass `details` for `precheckCompleted` (on both success and failure — see Phase 1), `environmentSetup`, `upgradeStepStarted`, and `upgradeStepCompleted`. +- **Status values**: `"succeeded"` | `"failed"` (must include `message`) | `"skipped"` (must include `message`). +- **SILENT**: Event reporting is internal telemetry only — NEVER mention `#report-event` calls, event names, or reporting status in user-facing messages. + +### Efficiency + +- **Targeted reads**: Use `grep` over full file reads; read sections, not entire files. +- **Quiet commands**: Use `-q`, `--quiet` for build/test when appropriate. +- **Single write for plan.md**: Generate the complete `plan.md` in one `create_file` call after gathering all information. Do NOT make multiple edits. +- **Incremental writes for progress.md**: Update `progress.md` incrementally as steps complete. + +### Session ID Consistency (CRITICAL) + +- `SESSION_ID` is generated in Phase 1 (Precheck) on success. Use this **exact** ID for ALL subsequent tool calls — never fabricate or change it. + +### Intermediate Version Strategy + +Use intermediates **when direct upgrade risks breaking builds**. A good intermediate has: + +- **Stability**: Stable LTS release with production track record +- **Compatibility bridge**: Bridges compatibility between current deps AND intermediates of other deps + +**Example**: Spring Boot 2.7.x is an effective intermediate for `Spring Boot 1.x → 3.x` because: + +- Final stable 2.x release (stability ✓) +- Supports Java 8-21 (wide compatibility range ✓) +- Uses javax.servlet (compatible with 1.x/2.x) with migration path to jakarta (3.x) ✓ + +Consider dependencies holistically — use target framework/Java as reference for intermediates. + +### Version Knowledge + +LLM training data may be outdated regarding the latest Java and Spring Boot releases. **Never reject a target version solely based on training data knowledge.** + +1. **Known stable/LTS versions to suggest by default** (non-exhaustive — newer stable or LTS releases may exist beyond this list): + - Java LTS: 11, 17, 21, 25 + - Spring Boot stable release lines: 2.7.x, 3.5.x, 4.0.x +2. **When the user requests a version you don't recognize**: Your training data may be stale. Use the `fetch` tool to verify the latest release information from the web before making any judgment. Only reject a version as invalid if the web lookup confirms it does not exist. Never reject based solely on training data. + +## Plan Format Specification + +When writing `plan.md`, generate the **complete file** in a single `create_file` call to `.github/java-upgrade//plan.md`. Follow this exact structure: + +### Plan Header + +```markdown +# Upgrade Plan: () + +- **Generated**: +- **HEAD Branch**: +- **HEAD Commit ID**: +``` + +### Section: Available Tools + +List ONLY the JDKs and build tools required/used during the upgrade (not all discovered ones). Use `#list-jdks` and `#list-mavens` results to check availability. Mark missing required JDKs as `****` with a note indicating which step needs it. **Exception — base (current) JDK**: If the project's current JDK version is not found, do NOT mark it as ``. The base JDK is only needed for the optional baseline step; if the user doesn't have it, baseline will be skipped. Note it as "not available (baseline will be skipped)". Mark build tools needing upgrade as `****`. If a wrapper is present, check the wrapper-defined version in `.mvn/wrapper/maven-wrapper.properties` or `gradle/wrapper/gradle-wrapper.properties`. Installation/upgrade happens during execution, not planning. + +**Build tool compatibility reference** (non-exhaustive — verify from official docs when uncertain): +- Maven 3.9+: required for Java 21 +- Maven 4.0+: required for Java 25 +- Gradle 8.5+: required for Java 21 +- Gradle 8.8+: required for Java 22 +- Gradle 9.1+: required for Java 25 +- maven-compiler-plugin 3.11+: required for Java 21 +- maven-surefire-plugin 3.1+: recommended for Java 17+ + +This section is finalized during Design & Review (after step sequence is known), not during Initialize & Analyze. + +Sample: +```markdown +## Available Tools + +**JDKs** +- JDK 1.8.0: /path/to/jdk-8 (current project JDK, used by step 2) +- JDK 17: **** (required by step 3) +- JDK 21: **** (required by step 6) + +**Build Tools** +- Maven 3.9.6: /path/to/maven +- Maven Wrapper: 3.8.1 → **** to 3.9.6+ (current version incompatible with Java 21) +``` + +### Section: Guidelines + +User-specified guidelines or constraints in bullet points. Extract from user's prompt if provided, or leave empty. + +Always include this user-facing note: +```markdown +> Note: You can add any specific guidelines or constraints for the upgrade process here if needed, bullet points are preferred. +``` + +### Section: Options + +```markdown +## Options + +- Working branch: appmod/java-upgrade- +- Run tests before and after the upgrade: true +``` + +These are user-configurable options. Never remove them. + +### Section: Upgrade Goals + +List ONLY user-requested target versions. These drive all other decisions. + +### Section: Technology Stack + +Table of core dependencies and compatibility with upgrade goals. Analyze ALL modules in multi-module projects. Only include direct dependencies + those critical for upgrade compatibility. Flag EOL dependencies with "⚠️ EOL" suffix. Include build tools and plugins. + +Columns: Technology/Dependency | Current | Min Compatible Version | Why Incompatible + +Sample: +```markdown +| Technology/Dependency | Current | Min Compatible | Why Incompatible | +| ------------------------ | ------- | -------------- | ---------------------------------------------- | +| Java | 8 | 21 | User requested | +| Spring Boot | 2.5.0 | 3.2.0 | User requested | +| Maven (wrapper) | 3.6.3 | 3.9.0 | Maven 3.6.x does not support Java 21 | +| maven-compiler-plugin | 3.8.1 | 3.11.0 | Older versions cannot compile Java 21 bytecode | +| javax.servlet ⚠️ EOL | 4.0 | N/A | Replaced by jakarta.servlet in Spring Boot 3.x | +| Lombok | 1.18.20 | 1.18.20 | - | +``` + +### Section: Derived Upgrades + +Required upgrades inferred from user targets based on compatibility rules. Each must have justification. + +Common derivations: +- Spring Boot 3.x → Java 17+, Jakarta EE 9+, Hibernate 6.x, Spring Framework 6.x +- Spring Boot 3.2+ → Spring Framework 6.1+ +- Spring Boot 4.x → Java 17+, Jakarta EE 10+, Spring Framework 7.x +- Java 21 → Maven 3.9+, Gradle 8.5+, maven-compiler-plugin 3.11+ +- Java 25 → Maven 4.0+, Gradle 9.1+ +- Build tool upgrade → update wrapper version + +### Section: Impact Analysis + +**This is the core of the plan.** A complete, file-level specification of every change required by the upgrade. Aim for completeness so the execution phase can apply changes by following this section alone. + +Organize findings into subsections: + +#### Subsection: Dependency Changes + +Table of all dependency/plugin/BOM changes in build files. + +Columns: File | Dependency | Current | Action | Target | Reason + +Actions: `upgrade`, `replace` (different artifact), `remove` (pin/override), `add` (new dep needed) + +Sample: +```markdown +| File | Dependency | Current | Action | Target | Reason | +|------|-----------|---------|--------|--------|--------| +| pom.xml | spring-boot-starter-parent | 2.7.18 | upgrade | 3.3.13 | User requested | +| pom.xml | java.version | 11 | upgrade | 21 | User requested | +| pom.xml | logback.version pin | 1.2.12 | remove | (managed) | Conflicts with SB 3.3 managed 1.5.x | +| app-service/pom.xml | httpclient | 4.5.x | replace | httpclient5 | Removed in Spring Framework 6.0 | +``` + +#### Subsection: Source Code Changes + +Table of all source file modifications required. + +Columns: File | Location | Current | Required Change | Reason + +Sample: +```markdown +| File | Location | Current | Required Change | Reason | +|------|----------|---------|----------------|--------| +| UserController.java | import line 3 | javax.servlet.http.HttpServletRequest | Replace with: jakarta.servlet.http.HttpServletRequest | Jakarta EE 10 namespace | +| SecurityConfig.java | class declaration | extends WebSecurityConfigurerAdapter | Rewrite to @Bean SecurityFilterChain | Removed in Spring Security 6.0 | +| HttpClientService.java | imports + API calls | org.apache.http.* | Rewrite to org.apache.hc.client5.* | HttpClient 4→5 API change | +``` + +#### Subsection: Configuration Changes + +Table of changes to config files (`application.properties`, `application.yml`, XML configs, etc.). Omit if no config changes needed. + +Columns: File | Property/Setting | Current | Required Change | Reason + +#### Subsection: CI/CD Changes + +Table of CI/CD file updates needed to match the upgraded JDK/runtime. + +Columns: File | Location | Current | Required Change + +Sample: +```markdown +| File | Location | Current | Required Change | +|------|----------|---------|----------------| +| Dockerfile | line 1, 12 | mcr.microsoft.com/openjdk/jdk:11-ubuntu | Change to: jdk:21-ubuntu | +| azure-pipelines.yml | line 8 | versionSpec: '11' | Change to: '21' | +``` + +#### Subsection: Risks & Warnings + +Items requiring special attention during execution — non-trivial rewrites, potential runtime-only issues, CVE-pinned versions, etc. Include genuine risks and medium-risk items with non-trivial mitigation, not routine changes. Include a mitigation strategy for each risk. + +Sample: +```markdown +- **SecurityConfig rewrite**: Non-trivial — Spring Security 6 DSL changed significantly (authorizeRequests → authorizeHttpRequests, antMatchers → requestMatchers, lambda DSL for CSRF). **Mitigation**: Verify security behavior with existing integration tests after rewrite; add a smoke test if none exist. +- **JDK reflection usage in FooService.java:42**: `setAccessible(true)` on java.lang.reflect.Field — compiles but throws InaccessibleObjectException at runtime on JDK 17+. No test coverage for this path. **Mitigation**: Apply deterministic rewrite using `MethodHandles.privateLookupIn()`, or add `--add-opens` as a temporary workaround with a TODO to remove it. +``` + +### Section: Upgrade Steps + +Step format: +```markdown +- Step N: + - **Rationale**: Why this step is needed and why at this position + - **Changes to Make**: Reference specific items from Impact Analysis + - **Verification**: Command, JDK, Expected Result +``` + +**Step design rules:** +- **Every step must leave the project in a compilable state.** Do not create steps that expect compilation failure — group related changes together so each step compiles cleanly. For multi-module projects, verify compilation at the reactor root level if individual module verification is impractical. +- **Reference Impact Analysis, don't duplicate it.** Steps should reference specific subsections or groups (e.g., "Apply all Dependency Changes for Spring Boot 3.x migration and corresponding Source Code Changes") rather than repeating every file change. +- **Fewer, coarser steps.** Group related changes (e.g., all Spring Boot 3.x migration changes in one step) rather than one step per file. + +**Mandatory steps:** + +- **Step 1 (MANDATORY)**: Setup Environment — Install required JDKs/build tools marked `` (do NOT install the base JDK if it is unavailable — it is only needed for the optional baseline). Verify with `#list-jdks`. Expected: All required JDKs available. +- **Step 2 (MANDATORY)**: Setup Baseline — If the base (current) JDK is available, run baseline compilation and tests with current JDK. Command: `mvn clean compile test-compile -q && mvn clean test -q`. Document SUCCESS/FAILURE, test pass rate (forms acceptance criteria). **If the base JDK is not available, skip this step** with status `"skipped"` and proceed to upgrade steps. +- **Steps 3-N**: Upgrade steps — apply all changes from Impact Analysis. Group related changes so each step compiles. Verify with `mvn clean test-compile -q` (compile only). +- **Final step (MANDATORY)**: Final Validation — Verify all goals met, resolve ALL TODOs and workarounds, clean rebuild with target JDK, run full test suite and fix ALL failures (iterative fix loop until 100% pass). Skip tests if disabled in Options. **For files flagged in Risks & Warnings as lacking test coverage, "compile + test pass" is NOT sufficient** — either (a) apply the deterministic rewrite as part of an upgrade step, or (b) document the residual runtime risk in `summary.md` Key Risks. Do not silently ship a latent JDK-version runtime bug. + +## Workflow + +### Phase 1: Precheck + +| Category | Scenario | Action (use `#askQuestions` tool when available and appropriate) | +| ------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Unsupported Project | Not a Java project | This path should not be reached — the upgrade agent is only invoked for Java projects. Do NOT call `#report-event`. Simply STOP and inform the user: "This project does not appear to be a Java project. The Java upgrade agent only supports Java projects." | +| Unsupported Project | Not a Maven/Gradle project | Check for alternative build systems: look for `build.xml` (Ant), `BUILD`/`BUILD.bazel` (Bazel), or other build files. If detected, call `#report-event` with details, then inform the user: "Detected [Ant/Bazel/other] build system. Maven and Gradle are fully supported; [Ant/Bazel/other] support is experimental and results may vary." Attempt to continue with best-effort analysis. If no recognizable build system is found, call `#report-event`, then STOP with error listing supported build systems (Maven, Gradle). | +| Invalid Goal | Missing target version | Do NOT call `#report-event` yet. Instead, analyze project dependencies (read `pom.xml`/`build.gradle` to detect current Java version, Spring Boot version, and other key deps), derive feasible upgrade options (e.g., Java 17, Java 21, Java 25, Spring Boot 3.2, Spring Boot 3.5, Spring Boot 4.0), and use `#askQuestions` to present those options as selectable choices for the user to pick the desired target(s). Only report `precheckCompleted` (succeeded or failed) after the user has selected a target or the interaction concludes. | +| Invalid Goal | Incompatible target combination | Call `#report-event`, then STOP and explain incompatibility | + +**On failure**: → `#report-event(event: "precheckCompleted", phase: "precheck", status: "failed", details: {category: "", scenario: ""}, message: "")` — **Call this FIRST** before stopping or asking users. Pass the failed category (e.g., "Unsupported Project", "Invalid Goal") and scenario from the table above. **IMPORTANT**: `details.category` and `details.scenario` are **REQUIRED** when status is "failed" — the tool will reject the call without them. **Exception**: For the "Missing target version" scenario, do NOT report failure immediately — interact with the user first (see table above) and only report `precheckCompleted` (succeeded or failed) after the user has selected a target or the interaction concludes. + +**On success**: → `#report-event(event: "precheckCompleted", phase: "precheck", status: "succeeded", details: {baseJdkVersion: "", targetVersion: ""})` — **This generates a new `SESSION_ID`. Use this `SESSION_ID` for all subsequent tool calls.** + +### Phase 2: Generate Upgrade Plan + +#### 1. Initialize & Analyze + +1. Call tool `#report-event(sessionId, event: "planGenerationStarted", phase: "plan", status: "succeeded")` — **FIRST action, before any file or version control operations** +2. **Detect version control availability**: Use `#version-control(sessionId: , workspacePath, action: "checkStatus")` to detect if git is available. If the response indicates version control is unavailable, set `GIT_AVAILABLE=false`. **Do not ask the user. Do not report failure.** +3. If `GIT_AVAILABLE=true`: Use `#version-control(sessionId: , workspacePath, action: "stashChanges", stashMessage: "java-upgrade-precheck-")` to stash any uncommitted changes. +4. **Project environment**: Extract user-specified guidelines. Detect all available JDKs/build tools via `#list-jdks(sessionId)`, `#list-mavens(sessionId)`. Detect wrapper presence and read wrapper properties if present. Check build tool version compatibility with target JDK — flag incompatible versions. +5. **Technology stack analysis**: Identify core tech stack across **ALL modules** — direct deps, upgrade-critical transitive deps, build tools, and build plugins (`maven-compiler-plugin`, `maven-surefire-plugin`, `maven-war-plugin`, etc.). Flag EOL dependencies. Determine compatibility against upgrade goals. +6. **Compatibility scan**: Perform a comprehensive scan for all upgrade-blocking patterns. + + **What to find:** + + | Dimension | What to look for | + |-----------|-----------------| + | JDK source compatibility | Reflection into java.base internals (`setAccessible` on JDK classes), internal package imports (`sun.misc.*`, `sun.reflect.*`, `jdk.internal.*`), removed/deprecated APIs for the target JDK version, JDK-removed modules needing explicit deps (JAXB, javax.activation, etc.) | + | Framework breaking changes | For each framework major version jump: fetch the official migration guide, extract all breaking change patterns (removed/renamed classes, changed package namespaces, removed config properties, deprecated-then-removed APIs, changed defaults), then search source code and config files for every pattern found | + | CI/CD configuration | All CI/CD files (Dockerfile, workflows, pipelines, Jenkinsfile, etc.) with hardcoded JDK/Java version references that need updating | + | Build plugin compatibility | Build plugins incompatible with the target JDK version | + | Dependency version conflicts | Explicit version pins/overrides in dependency management that conflict with the target framework's BOM | + | Test infrastructure | Test framework compatibility with target JDK/framework (JUnit 4→5 migration, Mockito version compatibility, Spring Test API changes, test utility class removals/renames) | + | Configuration properties | Renamed, removed, or restructured config properties in application.properties/application.yml (e.g., Spring Boot 2→3 renames like `spring.redis.*` → `spring.data.redis.*`, removed properties, changed defaults) | + | Runtime behavior changes | JDK runtime behavior differences that compile but fail at runtime: serialization format changes, default locale/charset changes, `HashMap`/`HashSet` iteration order assumptions, `SecurityManager` removal (JDK 17+), strong encapsulation of internal APIs | + | Resource/metadata files | Framework metadata files that changed format or location (e.g., Spring Boot 3: `META-INF/spring.factories` → `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`), `META-INF/services` entries, `persistence.xml`, `web.xml`, and other descriptor files | + + **Output requirement**: For each finding, record: file, line/location, current state, what needs to change, and why. Every finding must appear in the plan's Impact Analysis section. No known findings may be deferred to execution. Document known unknowns (e.g., transitive dependency conflicts only discoverable after version changes, runtime-only reflection issues) in Risks & Warnings with mitigation strategies. + +#### 2. Design & Review + +1. For incompatible deps in the Technology Stack, prefer: Replacement > Adaptation > Rewrite +2. Determine intermediate versions needed (see **Intermediate Version Strategy**) +3. Finalize Available Tools based on the planned step sequence; determine which JDK versions are required and at which steps; mark missing ones as ``, mark build tools needing upgrade as `` (including wrapper version if applicable). **Exception — base (current) JDK**: If the project's current JDK version is not found via `#list-jdks`, do **not** mark it as ``. The base JDK is only needed for the optional baseline step. Instead, note it as "not available (baseline will be skipped)". +4. Design upgrade steps — group related changes so each step leaves the project in a compilable state. No step should expect compilation failure. Reference Impact Analysis items rather than repeating details. +5. **Self-verify completeness**: Every finding from the Compatibility Scan must appear in the Impact Analysis section. Every item in Impact Analysis must be addressed by an Upgrade Step. If gaps are found, go back and fill them. +6. **Write complete `plan.md`** to `.github/java-upgrade//plan.md` using `create_file` — follow the **Plan Format Specification** above. Include all sections (Available Tools, Guidelines, Options, Upgrade Goals, Technology Stack, Derived Upgrades, Impact Analysis, Upgrade Steps) in a single write. If `GIT_AVAILABLE=false`, use "N/A" for branch/commit and include a notice about version control. +7. Verify all placeholders are filled, check for missing coverage/infeasibility/limitations. If issues found, rewrite the file. +8. Call tool `#report-event(sessionId, event: "planReviewed", phase: "plan", status: "succeeded")` + +### Phase 3: Confirm Plan with User (MANDATORY) + +1. Call tool `#confirm-upgrade-plan(sessionId)` — awaits user confirmation + +### Phase 4: Execute Upgrade Plan + +#### 1. Initialize + +1. Read `.github/java-upgrade//plan.md` for "Options" +2. Use `#version-control(sessionId: , workspacePath, action: "stashChanges")` to stash any uncommitted changes. Then use `#version-control(sessionId: , workspacePath, action: "createBranch", branchName: "appmod/java-upgrade-")` (or the branch defined in `plan.md`). If version control is unavailable (`GIT_AVAILABLE=false`), log warning in `plan.md` that changes are not version-controlled. +3. Update `.github/java-upgrade//progress.md`: + - Replace ``, `` and timestamp placeholders + - Create step entries for each step in `plan.md` (per **Template compliance** rule) +4. Call tool `#report-event(sessionId, event: "planExecutionStarted", phase: "execute", status: "succeeded")` + +#### 2. Execute: + +For each step: + +1. Read `.github/java-upgrade//plan.md` for step details and guidelines +2. Mark ⏳ in `.github/java-upgrade//progress.md` +3. Make changes as planned (use OpenRewrite if helpful, verify results) + - Add TODOs for any deferred work, e.g., temporary workarounds +4. **Review Code Changes** (per rules in `progress.md` template): Verify sufficiency (all required changes present) and necessity (no unnecessary changes, functional behavior preserved, security controls maintained). + - Add missing changes and revert unnecessary changes. Document any unavoidable behavior changes with justification. +5. Verify with specified command/JDK + - **Steps 1-N (Setup/Upgrade)**: Compilation must pass (including both main and test code, fix immediately if not). Test failures acceptable - document count. + - **Final Validation Step**: Achieve **Upgrade Success Criteria** - iterative test & fix loop until 100% pass (or ≥ baseline). NO deferring. **Skip test execution if "Run tests before and after the upgrade: false" in plan.md Options — only verify compilation in that case.** + - After each build (`mvn clean test-compile` or equivalent): `#report-event(sessionId, event: "buildCompleted", phase: "execute", status: "succeeded"|"failed")` + - After each test run (`mvn clean test` or equivalent): `#report-event(sessionId, event: "testCompleted", phase: "execute", status: "succeeded"|"failed")` +6. Commit using `#version-control(sessionId: , workspacePath, action: "commitChanges")` (if version control available; otherwise, log details in `progress.md`): + - commitMessage format — First line: `Step : - Compile: <result>` or `Step <x>: <title> - Compile: <result>, Tests: <pass>/<total> passed` (if tests run) + - Body: Changes summary + concise known issues/limitations (≤5 lines) + - **Security note**: If any security-related changes were made, include "Security: <change description and justification>" +7. Update `progress.md` with step details and mark ✅ or ❗ +8. Report event at end of each step: + - **Step 1 (Setup Environment)**: `#report-event(sessionId, event: "environmentSetup", phase: "execute", status: "succeeded"|"failed"|"skipped", details: {jdkPath: "<JDK path>", buildToolPath: "<build tool executable path>"})` — **details are REQUIRED** for this event. The `jdkPath` and `buildToolPath` must be valid paths that exist on this machine. Use `"."` for `buildToolPath` if a wrapper (mvnw/gradlew) is used. + - **Step 2 (Setup Baseline)**: `#report-event(sessionId, event: "baselineSetup", phase: "execute", status: "succeeded"|"failed"|"skipped")` — use `"skipped"` with a `message` when the base JDK is not available + - **Before each upgrade step (Steps 3-N)**: `#report-event(sessionId, event: "upgradeStepStarted", phase: "execute", status: "succeeded", details: {stepNumber: <N>, stepTitle: "<title>"})` + - **After each upgrade step (Steps 3-N)**: `#report-event(sessionId, event: "upgradeStepCompleted", phase: "execute", status: "succeeded"|"failed", details: {stepNumber: <N>, stepTitle: "<title>", commitId: "<commitId from #version-control response, or 'N/A' if version control unavailable>"})` + - **Final step (Final Validation)**: `#report-event(sessionId, event: "upgradeValidationCompleted", phase: "execute", status: "succeeded"|"failed", details: {stepNumber: <N>, stepTitle: "<title>", commitId: "<commit_id from #version-control response if version control available, otherwise 'N/A'>"})` + +#### 3. Complete + +1. Validate all steps in `plan.md` have ✅ in `.github/java-upgrade/<SESSION_ID>/progress.md` +2. Validate all **Upgrade Success Criteria** are met, or otherwise go back to Final Validation step to fix +3. Call tool `#report-event(sessionId, event: "planExecutionCompleted", phase: "execute", status: "succeeded")` + +### Phase 5: Summarize & Cleanup + +1. **Scan CVEs**: Extract direct deps (`mvn dependency:list -DexcludeTransitive=true`), call `#validate-cves-for-java(sessionId, dependencies, projectPath)` +2. **Collect test coverage**: Run `mvn clean verify -Djacoco.skip=false` or equivalent; record metrics +3. Generate `summary.md`: + - **Read spec**: Read `summary.template.md` (in the session directory) — it contains the format specification with rules and samples. + - **Write**: Collect all data from `progress.md`, build output, CVE scan results, and coverage metrics. Resolve OS username (`$env:USERNAME` / `$USER` / `whoami`). Write `summary.md` as a new file using `create_file` per **Template compliance**. + - **Self-check**: Scan the written `summary.md` for HTML comments, `<placeholder>` tokens, empty bullets, unfilled table cells, bare headings without content, duplicate section headings. Fix any issues found. +4. Clean up temp files; remove HTML comments from all `.md` files +5. → `#report-event(sessionId, event: "summaryGenerated", phase: "summarize", status: "succeeded", message: "<1-2 sentence summary>")` + +### Phase 6: Prompt for Follow-up Actions (CONDITIONAL) + +If issues detected, use `#askQuestions` to prompt user: + +1. **Critical/High CVEs found**: Offer to upgrade vulnerable dependencies using this custom agent; use `#validate-cves-for-java(sessionId)` to verify resolution. +2. **Low coverage (<70%)**: Offer to generate tests via `#generate-tests-for-java(sessionId, projectPath)`. + + +--- + +## Working Folder and Reporting + +When invoked from the appmod CLI orchestrator, the calling prompt will provide a +`modernization-work-folder` and a `TaskId`. The following rules are MANDATORY and +override any conflicting working-folder convention from the sections above: + +- Use `${modernization-work-folder}` as the working directory for ALL bookkeeping + artifacts (plan notes, progress logs, intermediate results). Do NOT create or + write into any folder outside `${modernization-work-folder}` for these artifacts + (e.g., do NOT use `.github/java-upgrade/<timestamp>/` or any other ad-hoc + location). Source-code edits inside the target repository are unaffected by + this rule. + +- Before returning, you MUST: + 1. Create `${modernization-work-folder}/${TaskId}/` if it does not exist. + 2. Write `${modernization-work-folder}/${TaskId}/modernization-summary.md` with: + - `finalStatus`: one of `"success"`, `"failed"`, `"skipped"` + - `successCriteriaStatus`: object with boolean `passBuild`, + `generateNewUnitTests`, `passUnitTests` + - `summary`: short prose summary of what changed + - `failureReason`: short prose, only when `finalStatus` is `"failed"` + 3. Return the same `finalStatus` / `successCriteriaStatus` / `summary` in your + final message so the orchestrator can update `tasks.json`. diff --git a/.github/skills/execute-modernization-task/SKILL.md b/.github/skills/execute-modernization-task/SKILL.md new file mode 100644 index 000000000..09bb4dd07 --- /dev/null +++ b/.github/skills/execute-modernization-task/SKILL.md @@ -0,0 +1,53 @@ +--- +name: execute-modernization-task +description: Execute a modernization task as part of a modernization plan +--- +# Role +You are a code migration agent that executes modernization tasks. You will change the code according to skills, migration requirement, environment configuration and success criteria + +# Principles +1) Reuse current branch when to do the code change +2) NEVER discard any change +3) If a relevant skill exists in the available skills list, load it for more information about the task. + +# Reading Provisioned Resources + +If `./infra/infra-config.md` exists, it contains information about Azure resources provisioned by an infrastructure task. This information can help you use correct resource names and endpoints in configuration templates. + +# Workflow +Follow these steps in order when executing a modernization task: + +1. **Extract Knowledge Base**: Load all relevant skills from the available skills list. Extract the best practices and migration guidance they contain. This knowledge base takes precedence over any general knowledge you have. +2. **Analyze and Migrate**: Analyze the current code and reason about each required code change based on the extracted knowledge base. When there is a conflict between your general knowledge and the skill-provided best practices, always follow the skill-provided best practices. +3. **Consistency Check**: After completing code migration, run the consistency check. +4. **Build and Test**: Build the source and run unit tests. The source must be buildable and no new test failures may be introduced by your changes. +5. **Re-verify After Any Change**: Every time you make a change — including consistency fixes — you must rebuild and re-run unit tests, even if the previous build and test run were successful. + +# Consistency Check + +Call custom agent `task` with the following prompt to run the consistency check: + + ```md + Call skill validation-check-consistency to validate the consistency of the migrated code. + - modernization-work-folder: ${modernization-work-folder} + - task-id: ${taskid} + - task-skill: The skill(s) used for this migration task, you should find it in ${modernization-work-folder}/.metadata/tasks.json + ``` + +Review the consistency check results. If any Critical or Major issues are found, fix them and re-run the consistency check. Repeat this fix-and-revalidate loop until the check reports zero Critical and zero Major issues before proceeding. + +# Exit Criteria +Before committing and marking the task as complete, verify: +1. **Consistency**: Fix all Critical and Major issues. Apply best-effort fixes for Minor issues. +2. **Completeness**: All old technology references relevant to this task are fully removed or replaced — check source files, configuration files, build files, and test files; do not leave partial old-technology remnants +3. **Build and tests**: If the task success criteria require `passBuild` or `passUnitTests`, confirm they pass before finishing + +Do not mark the task as complete until all applicable exit criteria are satisfied. + +# Final Check + +Run a full build and execute all unit tests one last time. Confirm there are no build errors and no unit test failures before proceeding to commit. + +# Output +1) Create a subfolder ${taskid} under ${modernization-work-folder}. You only need to generate a summary report "modernization-summary.md", under this subfolder to summarize the changes, and there is no need to generate any other documents. +2) Make a commit when the task is completed with the changes made in the modernization task. diff --git a/.github/skills/execute-upgrade-task/SKILL.md b/.github/skills/execute-upgrade-task/SKILL.md new file mode 100644 index 000000000..e4b7bb633 --- /dev/null +++ b/.github/skills/execute-upgrade-task/SKILL.md @@ -0,0 +1,23 @@ +--- +name: execute-upgrade-task +description: Execute an upgrade task as part of a modernization plan +--- +# Role +You are a code migration agent that executes upgrade tasks. You will change the code according to skills, migration requirement, environment configuration and success criteria + +# Principles +1) Reuse current branch when to do the code change +2) NEVER discard any change +3) If a relevant skill exists in the available skills list, load it for more information about the task. + +# Exit Criteria +Before committing and marking the task as complete, verify: +1. **Consistency**: All upgrade goals described in the task are correctly and completely implemented — re-read the task description and requirements and confirm every goal is addressed in the changed files +2. **Completeness**: All old technology references relevant to this task are fully removed or replaced — check source files, configuration files, build files, and test files; do not leave partial old-technology remnants +3. **Build and tests**: If the task success criteria require `passBuild` or `passUnitTests`, confirm they pass before finishing + +Do not mark the task as complete until all applicable exit criteria are satisfied. + +# Output +1) Create a subfolder ${taskid} under ${modernization-work-folder}. You only need to generate a summary report "modernization-summary.md", under this subfolder to summarize the changes, and there is no need to generate any other documents. +2) Make a commit when the task is completed with the changes made in the upgrade task. diff --git a/.github/skills/fact-application-name/SKILL.md b/.github/skills/fact-application-name/SKILL.md new file mode 100644 index 000000000..ec12cb292 --- /dev/null +++ b/.github/skills/fact-application-name/SKILL.md @@ -0,0 +1,112 @@ +--- +name: fact-application-name +description: Identify application name/identifier from configuration files +--- + +# Application Name Analysis + +## Purpose +Extract the application name or identifier from project files, configuration, and build descriptors. + +## Target Files/Locations +- **/pom.xml (<name>, <artifactId>) +- **/*.csproj (<AssemblyName>, <RootNamespace>) +- **/package.json (name field) +- **/build.gradle (rootProject.name) +- **/application.{properties,yml} (spring.application.name) +- **/README.md (title or project name) + +## Example Patterns +- `<name>CustomerPortal</name>` (Maven) +- `"name": "order-processor"` (Node.js) +- `spring.application.name=payment-service` +- `<AssemblyName>InventoryAPI</AssemblyName>` (.NET) + +## Analysis Steps + +### 1. Check Build File Names +``` +Maven (pom.xml): +Use Grep: "<name>|<artifactId>" +Extract: <name>ProjectName</name> + +Gradle (build.gradle): +Use Grep: "rootProject\\.name" +Extract: rootProject.name = 'project-name' + +Node.js (package.json): +Use Read and parse JSON: { "name": "app-name" } + +.NET (*.csproj): +Use Grep: "<AssemblyName>|<RootNamespace>" +``` + +### 2. Check Application Configuration +``` +Use Grep: "spring\\.application\\.name|app\\.name|application\\.name" +Files: **/application.{properties,yml}, **/appsettings.json +Context: -B 1 -A 1 +``` + +### 3. Check README +``` +Use Read: **/README.md (first 50 lines) +Look for: +- # Title +- Project name in first paragraph +- Badge labels +``` + +### 4. Check Container Names +``` +Use Grep: "container_name:" +Files: **/docker-compose*.yml +Application name often in container name +``` + +## Confidence Determination + +### High Confidence +- ✅ Name consistently appears across multiple files +- ✅ Clear identifier in build/config files +- **Example**: "Application name: CustomerPortal from pom.xml, spring.application.name, and README" + +### Medium Confidence +- ⚠️ Name in build file but not elsewhere +- ⚠️ Generic name (app, service, api) +- **Example**: "Project name: my-app (generic name from package.json only)" + +### Low Confidence +- ⚠️ No clear application name +- ⚠️ Names inconsistent across files +- **Example**: "Multiple names found, primary name unclear" + +### Not Applicable +- ❌ Multi-module project with multiple applications +- **Example**: "Monorepo with 10 microservices, no single application name" + +## Output Format + +```json +{ + "input_name": "Application Name", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Application name}", + "confidence": "high|medium|low", + "evidence": [ + "{Source file and field}", + "{Consistency across files}", + "{Alternative names found}" + ], + "values": [ + "{Primary name}", + "{Alternative names/identifiers}", + "{Artifact IDs or namespaces}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-application-port/SKILL.md b/.github/skills/fact-application-port/SKILL.md new file mode 100644 index 000000000..df9d5e3d7 --- /dev/null +++ b/.github/skills/fact-application-port/SKILL.md @@ -0,0 +1,96 @@ +--- +name: fact-application-port +description: Identify exposed application ports from container configuration +--- + +# Application Port Analysis + +## Purpose +Identify which ports the containerized application exposes for HTTP, HTTPS, and other services. + +## Target Files/Locations +- **/Dockerfile (EXPOSE instruction) +- **/docker-compose*.yml (ports section) +- **/k8s/**/*.yaml (containerPort, servicePort) +- **/application.{properties,yml} (server.port) + +## Example Patterns +- `EXPOSE 8080 443` +- `ports: - "3000:3000"` +- `containerPort: 8080` +- `server.port=8080` + +## Analysis Steps + +### 1. Check Dockerfile EXPOSE +``` +Use Grep: "^EXPOSE\\s+" +Files: **/Dockerfile, **/Containerfile +Extract port numbers +``` + +### 2. Analyze docker-compose Ports +``` +Use Read: **/docker-compose*.yml +Look for ports: section +Format: "HOST:CONTAINER" or just "PORT" +``` + +### 3. Check Kubernetes Manifests +``` +Use Grep: "containerPort|servicePort|targetPort" +Files: **/k8s/**/*.yaml, **/*.yaml +Context: -B 2 -A 2 +``` + +### 4. Check Application Configuration +``` +Use Grep: "server\\.port|PORT|HTTP_PORT" +Files: **/application.{properties,yml}, **/.env +``` + +## Confidence Determination + +### High Confidence +- ✅ EXPOSE in Dockerfile + ports in compose/k8s +- ✅ Application config matches container config +- **Example**: "Application exposes port 8080 (HTTP) and 443 (HTTPS) based on Dockerfile EXPOSE and docker-compose ports" + +### Medium Confidence +- ⚠️ Ports in config but not all files consistent +- **Example**: "Port 8080 in Dockerfile EXPOSE, but docker-compose uses 3000:8080" + +### Low Confidence +- ⚠️ No explicit port configuration +- **Example**: "No EXPOSE instruction, likely uses default runtime port" + +### Not Applicable +- ❌ No container or non-networked app +- **Example**: "CLI application, no network ports" + +## Output Format + +```json +{ + "input_name": "Application Port", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Ports summary}", + "confidence": "high|medium|low", + "evidence": [ + "{Dockerfile EXPOSE}", + "{docker-compose ports}", + "{K8s port config}", + "{Application config}" + ], + "values": [ + "{Port numbers: 8080, 443, etc.}", + "{Protocol: HTTP, HTTPS, TCP}", + "{Port mapping if applicable}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-application-type/SKILL.md b/.github/skills/fact-application-type/SKILL.md new file mode 100644 index 000000000..a171d88df --- /dev/null +++ b/.github/skills/fact-application-type/SKILL.md @@ -0,0 +1,133 @@ +--- +name: fact-application-type +description: Determine the type of application (Web App, API, Service, etc.) +--- + +# Application Type Analysis + +## Purpose +Identify the type of application based on code structure and dependencies. + +## Target Files/Locations +- **/pom.xml, **/build.gradle, **/build.gradle.kts (Java) +- **/*.csproj (C#/.NET) +- **/package.json (Node.js) +- **/requirements.txt, **/Pipfile, **/pyproject.toml (Python) +- **/*.java, **/*.cs, **/*.js, **/*.ts, **/*.py (source code) + +## Analysis Steps + +### 1. Check Java Web Frameworks +``` +Use Grep: "spring-boot-starter-web|@RestController|@RequestMapping|spring-boot-starter-webflux" +Files: **/pom.xml, **/build.gradle, **/*.java + +Map findings: +- spring-boot-starter-web / @RestController → REST API +- spring-boot-starter-webflux → REST API (Reactive) +``` + +### 2. Check Java gRPC +``` +Use Grep: "grpc|io\\.grpc" +Files: **/pom.xml, **/build.gradle, **/*.proto + +If found → gRPC Service +``` + +### 3. Check .NET Project Type +``` +Use Grep: "Microsoft\\.AspNetCore|Microsoft\\.NET\\.Sdk\\.Web" +Files: **/*.csproj + +Map findings: +- Microsoft.NET.Sdk.Web / Microsoft.AspNetCore → Web App / REST API +``` + +### 4. Check .NET gRPC +``` +Use Grep: "Grpc\\.AspNetCore" +Files: **/*.csproj + +If found → gRPC Service +``` + +### 5. Check .NET Background Service +``` +Use Grep: "BackgroundService|Microsoft\\.Extensions\\.Hosting" +Files: **/*.csproj, **/*.cs + +If found (and no web SDK) → Background Service +``` + +### 6. Check Node.js Frameworks +``` +Use Grep: "express|fastify|koa|@nestjs|@grpc/grpc-js" +Files: **/package.json + +Map findings: +- express / fastify / koa / @nestjs → REST API / Web App +- @grpc/grpc-js → gRPC Service +``` + +### 7. Check Python Frameworks +``` +Use Grep: "flask|django|fastapi|tornado|grpcio" +Files: **/requirements.txt, **/Pipfile, **/pyproject.toml + +Map findings: +- flask / django / fastapi / tornado → REST API / Web App +- grpcio → gRPC Service +``` + +### 8. Check for Batch/Job Indicators +``` +Use Glob: **/cron*, **/*job*, **/*batch*, **/*scheduler* +Use Grep: "@Scheduled|CronJob|BackgroundJob" +Files: **/*.{java,cs,py} + +If no web framework found but batch patterns detected → Batch Job +``` + +## Confidence Determination + +### High Confidence +- ✅ Web framework dependency clearly identified +- ✅ REST/gRPC annotations in source code +- **Example**: "REST API: Spring Boot with @RestController annotations" + +### Medium Confidence +- ⚠️ Framework found but type ambiguous +- **Example**: "ASP.NET Core project, could be Web App or API" + +### Low Confidence +- ⚠️ No clear framework indicators +- **Example**: "Unable to determine application type from available files" + +### Not Applicable +- ❌ Library project with no entry point +- **Example**: "Library/SDK project, no application type" + +## Output Format + +```json +{ + "input_name": "Application Type", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Application type}", + "confidence": "high|medium|low", + "evidence": [ + "{Framework dependencies}", + "{Annotations/decorators found}", + "{Project structure indicators}" + ], + "values": [ + "{Type: REST API, Web App, gRPC Service, Background Service, Batch Job, etc.}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-architecture-pattern/SKILL.md b/.github/skills/fact-architecture-pattern/SKILL.md new file mode 100644 index 000000000..cd3c5e80a --- /dev/null +++ b/.github/skills/fact-architecture-pattern/SKILL.md @@ -0,0 +1,218 @@ +--- +name: fact-architecture-pattern +description: Identify the application architecture pattern +--- + +# Architecture Pattern Analysis + +## Purpose +Determine the architectural pattern used in the application: Monolith, Microservices, Layered, Event-Driven, MVC, etc. + +## Analysis Strategy + +This SKILL performs a comprehensive analysis of project structure, dependencies, and code patterns to identify the architecture. + +## Analysis Steps + +### 1. Project Structure Analysis + +**Check for Microservices Indicators:** +```bash +# Use Glob to search for service directories +**/services/**/ +**/microservices/**/ +**/apps/**/ +``` + +**Check for Monolith Indicators:** +- Single deployable unit +- All code in one project/module + +### 2. Configuration & Infrastructure Analysis + +**Search for Service Discovery (Microservices):** +```bash +# Use Grep to find service discovery patterns +Pattern: "eureka|consul|etcd|kubernetes|k8s|service\.discovery" +Files: **/*.{yaml,yml,json,xml,properties,config} +``` + +**Search for API Gateway (Microservices):** +```bash +Pattern: "zuul|gateway|kong|ambassador|istio|envoy|traefik" +Files: **/*.{yaml,yml,json,xml,properties,pom.xml,build.gradle,package.json} +``` + +**Search for Message Brokers (Event-Driven):** +```bash +Pattern: "kafka|rabbitmq|activemq|azure\.servicebus|aws\.sqs|redis\.pub" +Files: **/*.{yaml,yml,json,xml,properties,config} +``` + +### 3. Code Pattern Analysis + +**MVC Pattern Detection:** +```bash +# Search for controllers +Pattern: "@Controller|@RestController|ApiController|[Controller]|class.*Controller" +Files: **/*.{java,cs,py,js,ts} + +# Search for models +Glob: **/models/**/*.*, **/entities/**/*.*, **/domain/**/*.* + +# Search for views +Glob: **/views/**/*.*, **/templates/**/*.* +``` + +**Layered Architecture Detection:** +```bash +# Search for layer separation +Glob: **/controller*/**/*, **/service*/**/*, **/repository*/**/*, **/dao*/**/*.* + +Pattern: "@Service|@Repository|@Component|[Service]|interface.*Repository" +Files: **/*.{java,cs} +``` + +**Event-Driven Pattern Detection:** +```bash +Pattern: "@EventHandler|@KafkaListener|@RabbitListener|EventEmitter|publish\(|subscribe\(" +Files: **/*.{java,cs,js,ts,py} +``` + +### 4. Deployment Configuration Analysis + +**Container Orchestration (Microservices):** +```bash +Glob: **/kubernetes/**/*.yaml, **/k8s/**/*.yaml, **/helm/**/*.* +Glob: docker-compose*.{yml,yaml} +``` + +**Check docker-compose for multiple services:** +```bash +Pattern: "services:\n(.*\n){3,}" +Files: docker-compose*.{yml,yaml} +``` + +## Analysis Decision Tree + +1. **If multiple deployable services detected** → Microservices Architecture + - Evidence: Multiple service directories, service discovery, API gateway + +2. **If message brokers + event handlers detected** → Event-Driven Architecture + - Evidence: Kafka/RabbitMQ configs, event listeners, pub/sub patterns + +3. **If clear MVC structure detected** → MVC Architecture + - Evidence: Controllers, Models, Views directories + +4. **If layer separation detected (but single deployment)** → Layered Monolith + - Evidence: Controller/Service/Repository layers in single project + +5. **If single project with no clear separation** → Simple Monolith + - Evidence: All code in one directory, minimal structure + +## Confidence Levels + +- **High**: Clear indicators from multiple sources (structure + config + code patterns) +- **Medium**: Some indicators present but mixed signals +- **Low**: Limited evidence, manual inspection recommended + +## Output Format + +After analysis, call the MCP tool: + +```javascript +write_assessment_result({ + resultJson: JSON.stringify({ + input_name: "Architecture Pattern", + analysis_method: "Hybrid", // Code + LLM analysis + status: "success", // or "not_applicable" or "failed" + result: { + finding: "Microservices Architecture", + confidence: "high", // high, medium, or low + evidence: [ + "Found 5 service directories: user-service, order-service, payment-service, notification-service, gateway-service", + "docker-compose.yml defines 5 separate services", + "Found Eureka service discovery configuration", + "API Gateway detected: Zuul configuration in gateway-service/application.yml" + ], + values: ["Microservices", "Service Discovery", "API Gateway"], + architecture_details: { + pattern: "Microservices", + service_count: 5, + has_service_discovery: true, + has_api_gateway: true, + has_message_broker: false + } + }, + execution_time_seconds: 3.5, + timestamp: new Date().toISOString() + }), + assessmentDir: variables.assessment_dir +}); +``` + +## Example Patterns by Technology Stack + +### Java/Spring Boot + +**Microservices:** +- `@EnableEurekaClient`, `@EnableDiscoveryClient` +- `spring-cloud-starter-netflix-eureka` +- Multiple `@SpringBootApplication` classes + +**Layered Monolith:** +- Single `@SpringBootApplication` +- Packages: `controller`, `service`, `repository`, `entity` + +### .NET/ASP.NET Core + +**Microservices:** +- Multiple `.csproj` files with `<OutputType>Exe</OutputType>` +- `Microsoft.Extensions.Http.Polly` (service-to-service calls) +- `Steeltoe.Discovery.Eureka` + +**MVC:** +- Single `.csproj` with `Microsoft.AspNetCore.Mvc` +- Folders: `Controllers`, `Models`, `Views` + +### Node.js/Express + +**Microservices:** +- Multiple `package.json` files in subdirectories +- `express-gateway`, `consul` dependencies + +**Layered:** +- Single `package.json` +- Folders: `routes`, `controllers`, `services`, `models` + +### Python + +**Microservices:** +- Multiple `app.py` or `main.py` files +- `nameko`, `flask-consul` dependencies + +**Event-Driven:** +- `kafka-python`, `pika` (RabbitMQ), `celery` +- Event handler decorators + +## Not Applicable Scenarios + +Return `status: "not_applicable"` if: +- Project is not an application (library, CLI tool only) +- Insufficient code to determine architecture +- Non-standard structure that doesn't fit patterns + +## Error Handling + +Return `status: "failed"` if: +- Unable to access project files +- Critical analysis error occurred + +Include error details in the result. + +## Notes + +- Architecture can be hybrid (e.g., Layered + Event-Driven) +- Include all applicable patterns in `values` array +- Prioritize most dominant pattern in `finding` +- If uncertain between patterns, use `confidence: "medium"` and explain in evidence diff --git a/.github/skills/fact-base-image/SKILL.md b/.github/skills/fact-base-image/SKILL.md new file mode 100644 index 000000000..f48912217 --- /dev/null +++ b/.github/skills/fact-base-image/SKILL.md @@ -0,0 +1,102 @@ +--- +name: fact-base-image +description: Identify container base image used in Dockerfile +--- + +# Base Image Analysis + +## Purpose +Identify the base container image (e.g., alpine:3.15, ubuntu:20.04, node:16-alpine) used in Dockerfile/Containerfile to understand OS dependencies and image foundations. + +## Target Files/Locations +- **/Dockerfile, **/Containerfile +- **/docker-compose*.yml +- **/k8s/**/*.yaml + +## Example Patterns +- `FROM alpine:3.15` +- `FROM ubuntu:20.04` +- `FROM node:16-alpine AS builder` +- `FROM mcr.microsoft.com/dotnet/sdk:6.0` + +## Analysis Steps + +### 1. Find and Read Dockerfiles +``` +Use Glob: **/Dockerfile, **/Containerfile +Use Read to examine each file +Look for FROM instructions +``` + +### 2. Extract Base Images +``` +Use Grep: "^FROM\\s+" +Files: **/Dockerfile, **/Containerfile +Context: -A 1 + +Parse format: FROM <image>:<tag> [AS <stage>] +``` + +### 3. Check for Multi-stage Builds +``` +Multiple FROM instructions indicate multi-stage build +Identify final stage base image +Note all intermediate base images +``` + +### 4. Verify Image Registry +``` +Check for: +- Docker Hub (no prefix or docker.io/) +- MCR (mcr.microsoft.com/) +- GCR (gcr.io/) +- ECR (*.amazonaws.com/) +- Custom registry +``` + +## Confidence Determination + +### High Confidence +- ✅ Dockerfile exists with explicit FROM instruction +- ✅ Image name and tag clearly specified +- **Example**: "Base image: alpine:3.15 from Dockerfile line 1" + +### Medium Confidence +- ⚠️ Multiple stages with different base images +- ⚠️ FROM uses build argument (FROM ${BASE_IMAGE}) +- **Example**: "Base image uses variable, likely node:16 based on docker-compose" + +### Low Confidence +- ⚠️ No Dockerfile found +- ⚠️ Base image unclear from manifests +- **Example**: "Kubernetes deployment exists but base image not specified" + +### Not Applicable +- ❌ No containerization +- **Example**: "No container files found" + +## Output Format + +```json +{ + "input_name": "Base Image", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Base image identified}", + "confidence": "high|medium|low", + "evidence": [ + "{Dockerfile location}", + "{FROM instruction(s)}", + "{Multi-stage details if applicable}" + ], + "values": [ + "{Base image name:tag}", + "{OS type (Alpine, Ubuntu, etc.)}", + "{Registry source}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-communication-protocols/SKILL.md b/.github/skills/fact-communication-protocols/SKILL.md new file mode 100644 index 000000000..6b6a9082f --- /dev/null +++ b/.github/skills/fact-communication-protocols/SKILL.md @@ -0,0 +1,118 @@ +--- +name: fact-communication-protocols +description: Identify inter-service communication protocols (HTTP/REST, gRPC, Message Queue, TCP) +--- + +# Communication Protocols Analysis + +## Purpose +Detect communication protocols used for inter-service communication, API exposure, and external integrations. + +## Target Files/Locations +- **/pom.xml, **/build.gradle, **/*.csproj, **/package.json (dependencies) +- **/*.proto (gRPC definitions) +- **/application.{properties,yml} (message queue configs) +- **/*.java, **/*.cs, **/*.js (HTTP clients, gRPC stubs) + +## Example Patterns +- **HTTP/REST**: RestTemplate, HttpClient, axios, fetch, @RestController +- **gRPC**: grpc-*, *.proto files, gRPC stubs +- **Message Queues**: RabbitMQ, Kafka, Redis Pub/Sub, AWS SQS +- **TCP/UDP**: Socket, ServerSocket, TcpClient +- **WebSocket**: ws://, WebSocket, SignalR + +## Analysis Steps + +### 1. Check for HTTP/REST +``` +Use Grep: "@RestController|@RequestMapping|HttpClient|RestTemplate|axios|fetch" +Files: **/*.{java,cs,js,py} +Context: -B 2 -A 2 + +Check Spring: @RestController, @GetMapping +Check .NET: HttpClient, [ApiController] +Check Node: express, axios +``` + +### 2. Check for gRPC +``` +Use Glob: **/*.proto +Count proto files + +Use Grep: "grpc|GrpcClient|GrpcChannel" +Files: **/*.{java,cs,go,py} +Dependencies: grpc-netty, Grpc.Net.Client, @grpc/grpc-js +``` + +### 3. Check for Message Queues +``` +Use Grep: "RabbitTemplate|KafkaTemplate|@JmsListener|IMessageQueue" +Files: **/*.{java,cs} + +Dependencies: +- spring-boot-starter-amqp (RabbitMQ) +- spring-kafka (Kafka) +- MassTransit, NServiceBus (.NET) + +Config: +- spring.rabbitmq.*, spring.kafka.* +``` + +### 4. Check for WebSocket +``` +Use Grep: "WebSocket|ws://|wss://|SignalR|@ServerEndpoint" +Files: **/*.{java,cs,js} +``` + +### 5. Check for TCP/UDP +``` +Use Grep: "ServerSocket|Socket|TcpListener|UdpClient" +Files: **/*.{java,cs,go} +``` + +## Confidence Determination + +### High Confidence +- ✅ Multiple protocol indicators found +- ✅ Dependencies + code usage confirmed +- **Example**: "HTTP/REST via Spring MVC, gRPC for internal services (5 .proto files), Kafka for async messaging" + +### Medium Confidence +- ⚠️ Dependencies present but usage unclear +- **Example**: "HTTP REST API likely, gRPC dependency but no .proto files found" + +### Low Confidence +- ⚠️ Protocol inferred from framework +- **Example**: "Likely HTTP-based (web framework) but protocol not explicit" + +### Not Applicable +- ❌ Standalone application with no communication +- **Example**: "CLI tool with no network communication" + +## Output Format + +```json +{ + "input_name": "Communication Protocols", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Protocols summary}", + "confidence": "high|medium|low", + "evidence": [ + "{HTTP/REST endpoints}", + "{gRPC proto files}", + "{Message queue configs}", + "{Dependencies}" + ], + "values": [ + "{Protocol: HTTP/REST, gRPC, Kafka, RabbitMQ, etc.}", + "{Framework: Spring MVC, ASP.NET Core, Express}", + "{Port numbers}", + "{Count: N endpoints, M proto files}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-compliance-requirements/SKILL.md b/.github/skills/fact-compliance-requirements/SKILL.md new file mode 100644 index 000000000..4de230ea0 --- /dev/null +++ b/.github/skills/fact-compliance-requirements/SKILL.md @@ -0,0 +1,131 @@ +--- +name: fact-compliance-requirements +description: Identify regulatory compliance needs (GDPR, HIPAA, PCI-DSS, SOX) +--- + +# Compliance Requirements Analysis + +## Purpose +Identify regulatory compliance requirements the application must meet based on data handling, industry, and security measures. + +## Target Files/Locations +- **/README.md, **/COMPLIANCE.md, **/SECURITY.md, **/privacy-policy.md +- **/docs/**/*.md +- **/*.{java,cs,js,py,ts} (data handling, encryption) +- **/application.{properties,yml} (audit logging) + +## Example Patterns +- GDPR: data protection, right to erasure, consent +- HIPAA: PHI, encryption, audit logs +- PCI-DSS: payment data, encryption at rest/transit +- SOX: financial data, audit trails, access controls + +## Analysis Steps + +### 1. Check Documentation +``` +Use Read: **/README.md, **/COMPLIANCE.md, **/SECURITY.md +Use Grep: "GDPR|HIPAA|PCI-DSS|PCI|SOX|Sarbanes-Oxley|ISO 27001|SOC 2" +Files: **/docs/**/*.md +Context: -B 2 -A 3 + +Look for: +- Compliance statements +- Regulatory requirements +- Data handling policies +``` + +### 2. Check for Data Protection Code +``` +Use Grep: "PersonalData|PII|PHI|PaymentData|encrypt|anonymize|pseudonymize" +Files: **/*.{java,cs,js,py} +Context: -B 2 -A 2 + +Indicators: +- Data encryption implementations +- Anonymization functions +- Consent management +- Data retention policies +``` + +### 3. Check for Audit Logging +``` +Use Grep: "AuditLog|audit|trail|compliance.*log" +Files: **/*.{java,cs}, **/application.{properties,yml} + +Look for: +- Audit event logging +- User action tracking +- Data access logs +``` + +### 4. Check for Data Privacy Features +``` +Search for: +- Data export (GDPR right to portability) +- Data deletion (right to erasure) +- Consent management +- Cookie policies + +Use Grep: "exportData|deleteUser|consent|privacy|GDPR" +``` + +### 5. Check Security Measures +``` +Compliance often requires: +- Encryption at rest and in transit +- Access controls (RBAC) +- Multi-factor authentication +- Regular security audits + +Cross-reference with security-implementation analysis +``` + +## Confidence Determination + +### High Confidence +- ✅ Explicit compliance documentation +- ✅ Compliance-specific code features +- ✅ Regulatory requirements mentioned +- **Example**: "GDPR compliant: data encryption, consent management, right-to-erasure API implemented" + +### Medium Confidence +- ⚠️ Some compliance features but not comprehensive +- **Example**: "Encryption and audit logging present, suggests PCI-DSS consideration but not documented" + +### Low Confidence +- ⚠️ No explicit compliance requirements +- ⚠️ Features that could support compliance +- **Example**: "Standard security features, no specific compliance mentioned" + +### Not Applicable +- ❌ Internal tool with no regulatory requirements +- **Example**: "Development utility, no compliance requirements" + +## Output Format + +```json +{ + "input_name": "Compliance Requirements", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Compliance summary}", + "confidence": "high|medium|low", + "evidence": [ + "{Documentation mentions}", + "{Compliance-specific code}", + "{Security features}", + "{Audit logging}" + ], + "values": [ + "{Regulations: GDPR, HIPAA, PCI-DSS, SOX, etc.}", + "{Features: encryption, audit logs, consent}", + "{Data types: PII, PHI, payment data}", + "{Certifications if mentioned}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-container-engine/SKILL.md b/.github/skills/fact-container-engine/SKILL.md new file mode 100644 index 000000000..4d30e5ecb --- /dev/null +++ b/.github/skills/fact-container-engine/SKILL.md @@ -0,0 +1,108 @@ +--- +name: fact-container-engine +description: Identify container runtime being used (Docker, Podman, containerd) +--- + +# Container Engine Analysis + +## Purpose +Determine which container engine the application uses (Docker, Podman, containerd, etc.) through analysis of configuration files, build scripts, and deployment manifests. + +## Target Files/Locations +- **/Dockerfile, **/Containerfile +- **/docker-compose*.yml +- **/.dockerignore +- **/Makefile, **/*.sh (build scripts) +- **/k8s/**/*.yaml, **/*.yaml (Kubernetes manifests) +- **/README.md, **/docs/**/*.md (documentation) +- **/.github/workflows/*.yml, **/.gitlab-ci.yml (CI/CD) + +## Example Patterns to Search +- **Docker**: `docker build`, `docker-compose`, `FROM`, `ENTRYPOINT`, `.dockerignore` +- **Podman**: `podman build`, `podman-compose`, `podman run` +- **containerd**: `ctr`, `nerdctl` +- **Build tools**: `docker buildx`, `buildah`, `kaniko` + +## Analysis Steps + +### 1. Check for Dockerfile/Containerfile +``` +Use Glob: **/Dockerfile, **/Containerfile +- Dockerfile indicates Docker usage +- Containerfile indicates Podman/Buildah compatibility +``` + +### 2. Search for Docker Compose Files +``` +Use Glob: **/docker-compose*.yml +Indicates Docker Compose usage +``` + +### 3. Analyze Build Scripts +``` +Use Grep: "docker|podman|buildah|nerdctl" +Files: **/*.sh, **/Makefile, **/*.bash +Context: -B 2 -A 2 +``` + +### 4. Check CI/CD Pipelines +``` +Use Grep in CI files: +- Pattern: "docker|podman|container" +- Files: **/.github/workflows/*.yml, **/.gitlab-ci.yml +``` + +### 5. Review Documentation +``` +Use Grep in docs: +- Pattern: "docker|podman|container engine" +- Files: **/README.md, **/docs/**/*.md +``` + +## Confidence Determination + +### High Confidence +- ✅ Dockerfile + docker-compose.yml present +- ✅ Build scripts explicitly use docker commands +- ✅ CI/CD uses specific container engine +- **Example**: "Docker engine with Compose v2 based on docker-compose.yml and Dockerfile" + +### Medium Confidence +- ⚠️ Generic Containerfile (Docker/Podman compatible) +- ⚠️ Build scripts don't specify engine +- **Example**: "Containerfile present, compatible with Docker or Podman" + +### Low Confidence +- ⚠️ No explicit engine indicators +- ⚠️ Only Kubernetes manifests (engine unclear) +- **Example**: "Container usage implied but engine unclear" + +### Not Applicable +- ❌ No containerization +- **Example**: "No container files found - traditional deployment" + +## Output Format + +```json +{ + "input_name": "Container Engine", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Container engine identified}", + "confidence": "high|medium|low", + "evidence": [ + "{Files found}", + "{Commands in scripts}", + "{CI/CD configuration}" + ], + "values": [ + "{Engine name: Docker, Podman, etc.}", + "{Compose version if applicable}", + "{Build tools used}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-container-version/SKILL.md b/.github/skills/fact-container-version/SKILL.md new file mode 100644 index 000000000..b18e4faf5 --- /dev/null +++ b/.github/skills/fact-container-version/SKILL.md @@ -0,0 +1,99 @@ +--- +name: fact-container-version +description: Identify container runtime version (Docker, Podman version) +--- + +# Container Version Analysis + +## Purpose +Determine the version of the container runtime being used through CI/CD configurations, documentation, or system requirements. + +## Target Files/Locations +- **/.github/workflows/*.yml, **/.gitlab-ci.yml +- **/README.md, **/docs/**/*.md +- **/Makefile, **/*.sh +- **/docker-compose.yml (version field) + +## Example Patterns +- `docker-compose version: '3.8'` +- Docker version: 20.10.17 +- Podman version: 4.1.0 + +## Analysis Steps + +### 1. Check docker-compose Version +``` +Use Glob: **/docker-compose*.yml +Use Read to find: version: '3.x' or version: '2.x' +This indicates required Docker Compose version +``` + +### 2. Search CI/CD for Version Specs +``` +Use Grep: "docker.*version|container.*version" +Files: **/.github/workflows/*.yml, **/.gitlab-ci.yml +Context: -B 2 -A 2 +``` + +### 3. Check Documentation +``` +Use Grep: "docker.*[0-9]+\\.[0-9]+|podman.*[0-9]+\\.[0-9]+" +Files: **/README.md, **/docs/**/*.md +Context: -B 1 -A 1 +``` + +### 4. Look for Version Requirements +``` +Search for: +- "requires Docker 20.10+" +- "tested with Podman 4.x" +- Minimum version specifications +``` + +## Confidence Determination + +### High Confidence +- ✅ docker-compose version explicitly set +- ✅ CI/CD specifies container version +- ✅ Documentation states version requirements +- **Example**: "Docker 20.10.17 specified in CI/CD, docker-compose v3.8" + +### Medium Confidence +- ⚠️ docker-compose version found but engine version unclear +- ⚠️ Version mentioned in docs but not enforced +- **Example**: "docker-compose v3.8 indicates Docker 19.03+ requirement" + +### Low Confidence +- ⚠️ No explicit version information +- ⚠️ Version inferred from features used +- **Example**: "Features used suggest Docker 20.10+ but not specified" + +### Not Applicable +- ❌ No containerization +- **Example**: "No container configuration found" + +## Output Format + +```json +{ + "input_name": "Container Version", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Container version information}", + "confidence": "high|medium|low", + "evidence": [ + "{docker-compose version}", + "{CI/CD specifications}", + "{Documentation references}" + ], + "values": [ + "{Engine: Docker/Podman}", + "{Version number or requirement}", + "{Compose version if applicable}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-data-classification/SKILL.md b/.github/skills/fact-data-classification/SKILL.md new file mode 100644 index 000000000..4cf84c614 --- /dev/null +++ b/.github/skills/fact-data-classification/SKILL.md @@ -0,0 +1,151 @@ +--- +name: fact-data-classification +description: Identify data sensitivity classification (Public, Internal, Confidential, PII) +--- + +# Data Classification Analysis + +## Purpose +Determine the sensitivity classification of data handled by the application to understand protection requirements. + +## Target Files/Locations +- **/README.md, **/SECURITY.md, **/DATA_CLASSIFICATION.md +- **/docs/**/*.md (data classification) +- **/*.{java,cs,js,py} (data annotations, comments) +- **/schema.sql, **/migrations/*.sql (database schemas) +- **/*.proto, **/*.graphql (API schemas) + +## Example Patterns +- **Public**: publicly accessible data, no protection needed +- **Internal**: company data, standard access controls +- **Confidential**: sensitive business data, strict access controls +- **PII**: personal identifiable information (names, emails, SSN) +- **PHI**: protected health information +- **PCI**: payment card data +- **Restricted**: highly sensitive, executive/legal only + +## Analysis Steps + +### 1. Check Documentation +``` +Use Read: **/README.md, **/SECURITY.md, **/DATA_CLASSIFICATION.md +Use Grep: "data classification|sensitivity|PII|confidential|restricted" +Files: **/docs/**/*.md +Context: -B 2 -A 3 + +Look for: +- Data classification policy +- Sensitivity levels +- Data types handled +``` + +### 2. Analyze Database Schemas +``` +Use Glob: **/schema.sql, **/migrations/*.sql, **/flyway/**/*.sql +Use Read to examine table definitions + +Look for sensitive data indicators: +- email, phone, address (PII) +- ssn, tax_id, national_id (PII) +- credit_card, account_number (PCI) +- password, secret, token (Credentials) +- medical, diagnosis (PHI) +- salary, financial (Confidential) +``` + +### 3. Check Code for Data Annotations +``` +Use Grep: "@PersonalData|@Confidential|@Restricted|@PII|@Sensitive" +Files: **/*.{java,cs} +Context: -B 1 -A 3 + +.NET: [PersonalData], [ProtectedPersonalData] +Java: Custom annotations or comments +``` + +### 4. Analyze API Schemas +``` +Use Glob: **/*.proto, **/*.graphql, **/openapi.yaml +Read schema definitions + +Identify sensitive fields: +- User personal information +- Authentication credentials +- Financial data +- Health information +``` + +### 5. Check Encryption Usage +``` +Encryption usage indicates sensitive data: + +Use Grep: "encrypt|cipher|AES|RSA|hash" +Files: **/*.{java,cs,js,py} +Context: -B 2 -A 2 + +What's encrypted: +- Passwords → Credentials (Confidential) +- Payment info → PCI Data +- Medical records → PHI +- Personal details → PII +``` + +### 6. Categorize Data Types +``` +Based on findings, classify: +- Public: blog posts, product catalogs +- Internal: employee directory, internal docs +- Confidential: trade secrets, financial reports +- PII: customer names, emails, addresses +- PHI: medical records, diagnoses +- PCI: credit card numbers, CVV +``` + +## Confidence Determination + +### High Confidence +- ✅ Data classification documented +- ✅ Sensitive data fields identified in schema +- ✅ Encryption applied to sensitive data +- **Example**: "Data classification: Confidential and PII - handles customer names, emails, addresses (PII) and financial transactions (Confidential)" + +### Medium Confidence +- ⚠️ Data types identified but no formal classification +- **Example**: "Database contains email and address fields (likely PII) but no classification policy documented" + +### Low Confidence +- ⚠️ Data types unclear from schema +- ⚠️ Generic field names +- **Example**: "Database schema generic, data sensitivity unclear" + +### Not Applicable +- ❌ No data storage (stateless API proxy) +- **Example**: "Stateless authentication proxy, no data stored" + +## Output Format + +```json +{ + "input_name": "Data Classification", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Classification summary}", + "confidence": "high|medium|low", + "evidence": [ + "{Documentation}", + "{Database schema analysis}", + "{Sensitive field identification}", + "{Encryption usage}" + ], + "values": [ + "{Classification levels: Public, Internal, Confidential, PII, etc.}", + "{Sensitive data types handled}", + "{Protection measures per classification}", + "{Data volume estimates if available}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-embedded-language-usage/SKILL.md b/.github/skills/fact-embedded-language-usage/SKILL.md new file mode 100644 index 000000000..90c68df15 --- /dev/null +++ b/.github/skills/fact-embedded-language-usage/SKILL.md @@ -0,0 +1,277 @@ +--- +name: fact-embedded-language-usage +description: Identifies whether the Java app executes or embeds code in another language or interpreter at runtime +--- + +# Embedded Language Usage Analysis + +## Purpose +Detect if the application executes external processes, embeds scripting engines, or dynamically evaluates code in other languages at runtime. This includes shell command execution, JavaScript engines, Python interpreters, Groovy shells, and embedded SQL/native code execution. + +## Target Files/Locations +- **Java source files**: **/*.java +- **Script files**: **/*.sh, **/*.bat, **/*.ps1 +- **Configuration files**: **/application.{properties,yml}, **/pom.xml, **/build.gradle +- **Resources**: **/resources/**/*.js, **/resources/**/*.py, **/resources/**/*.groovy + +## Example Patterns to Search +- **Process Execution**: `Runtime.getRuntime().exec()`, `ProcessBuilder`, `ProcessBuilder.command()` +- **Script Engines**: `ScriptEngineManager`, `ScriptEngine.eval()`, `Nashorn`, `GraalVM.Polyglot` +- **Embedded Interpreters**: `GroovyShell`, `PythonInterpreter`, `JRuby`, `Jython` +- **Dynamic Evaluation**: `eval()`, `compile()`, `interpret()` +- **Native Code**: `System.loadLibrary()`, `JNI`, `JNA`, `native` keyword +- **SQL Execution**: `Statement.execute()`, `PreparedStatement`, dynamic SQL construction + +## Analysis Steps + +### 1. Search for Process Execution +``` +Use Grep tool to search for external process execution: +- Pattern: "Runtime\\.getRuntime\\(\\)\\.exec|ProcessBuilder|exec\\(|startProcess" +- Files: **/*.java +- Context: -B 2 -A 2 (2 lines before/after for context) + +Look for: +- Shell command execution +- External program invocation +- Dynamic command construction +``` + +### 2. Search for ScriptEngine APIs +``` +Use Grep tool to find scripting engine usage: +- Pattern: "ScriptEngineManager|ScriptEngine|Nashorn|GraalVM|Polyglot" +- Files: **/*.java +- Context: -B 3 -A 3 + +Check for: +- ScriptEngineManager instantiation +- JavaScript, Python, Groovy engine loading +- eval() or compile() calls +``` + +### 3. Search for Embedded Language Interpreters +``` +Use Grep tool to detect interpreter embedding: +- Pattern: "GroovyShell|PythonInterpreter|JRuby|Jython|LuaJ|BeanShell" +- Files: **/*.java +- Context: -B 3 -A 3 + +Identify: +- Groovy script execution +- Python code embedding +- Ruby/Lua interpreters +``` + +### 4. Check for Native Library Loading +``` +Use Grep tool to find native code usage: +- Pattern: "System\\.loadLibrary|System\\.load|native\\s+\\w+|JNI|JNA" +- Files: **/*.java +- Context: -B 2 -A 2 + +Look for: +- JNI native method declarations +- Dynamic library loading +- JNA interface definitions +``` + +### 5. Analyze Dependencies +``` +Use Glob to find build files: +- **/pom.xml +- **/build.gradle +- **/build.gradle.kts + +Use Read to check for dependencies: +- groovy-all, groovy-core +- jython, jruby +- nashorn-core, graalvm-js +- jna, jna-platform +- rhino (Mozilla JavaScript engine) +``` + +### 6. Check for Dynamic SQL Execution +``` +Use Grep tool to find dynamic SQL: +- Pattern: "Statement\\.execute|executeUpdate|executeQuery|createStatement" +- Files: **/*.java +- Context: -B 3 -A 3 + +Analyze for: +- Dynamic SQL string construction +- Concatenated SQL queries +- PreparedStatement vs Statement usage +``` + +### 7. Scan for Script Files in Resources +``` +Use Glob to find embedded scripts: +- **/resources/**/*.js +- **/resources/**/*.py +- **/resources/**/*.groovy +- **/resources/**/*.lua + +If found, these may be loaded and executed at runtime +``` + +## Confidence Determination + +### High Confidence Criteria +Evidence of actual embedded language execution with clear implementation: +- ✅ ScriptEngineManager or ProcessBuilder instantiated with specific parameters +- ✅ Code shows eval() or execute() calls with script content +- ✅ Dependencies include scripting engines (groovy, jython, nashorn) +- ✅ Script files found in resources directory +- ✅ Multiple instances of embedded language usage across codebase + +**Examples**: +- "ScriptEngineManager found in MainController.java:45 with engine.eval(jsCode)" +- "GroovyShell.evaluate() called in RuleEngine.java with 15 .groovy files in resources/" +- "ProcessBuilder executing bash scripts found in 8 locations" + +### Medium Confidence Criteria +Partial evidence or indirect indicators: +- ⚠️ Dependencies include scripting engines but no direct usage found +- ⚠️ Process execution found but limited to standard system commands +- ⚠️ Native library loading present but unclear purpose +- ⚠️ SQL execution detected but mostly using PreparedStatement +- ⚠️ Comments reference scripting but no implementation found + +**Examples**: +- "Groovy dependency in pom.xml but no GroovyShell usage detected" +- "Runtime.exec() called once in utility class for system info" +- "Native library loaded but JNI methods not clearly identified" + +### Low Confidence Criteria +Weak or ambiguous evidence: +- ⚠️ Only standard JDBC operations (no dynamic SQL) +- ⚠️ No scripting dependencies detected +- ⚠️ Process execution in test code only +- ⚠️ Commented-out embedded language code +- ⚠️ Assumptions based on project type + +**Examples**: +- "Standard PreparedStatement usage only, no dynamic SQL" +- "No scripting engine references found" +- "Process execution only in unit tests for test setup" + +### Not Applicable Criteria +When embedded language analysis doesn't apply: +- ❌ Pure Java application with no external language integration +- ❌ Library project with no executable code +- ❌ Static website or documentation-only repository +- ❌ Different platform (e.g., .NET project being analyzed for Java patterns) + +**Examples**: +- "Pure Java Spring Boot REST API with no scripting requirements" +- "Maven plugin project with no runtime execution" +- "Documentation repository with no code" + +## Output Format + +**CRITICAL**: Use the `write_assessment_result` tool (not just output JSON text). + +```json +{ + "input_name": "Embedded Language Usage", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Clear 1-2 sentence summary of embedded language usage}", + "confidence": "high|medium|low", + "evidence": [ + "{Specific file path + line number where usage detected}", + "{Type of embedded language (Groovy, JavaScript, shell, etc.)}", + "{Dependencies or script files found}", + "{Context of usage (business rules, data processing, etc.)}" + ], + "values": [ + "{Scripting engine name and version}", + "{Number of usage instances}", + "{Types of scripts found}", + "{Native libraries loaded}" + ] + }, + "execution_time_seconds": {elapsed_time}, + "timestamp": "{ISO 8601 timestamp}" +} +``` + +**Finding Examples**: +- ✅ Good: "Application uses GroovyShell to execute business rules with 23 .groovy scripts in resources/rules/ directory" +- ✅ Good: "ScriptEngineManager loads Nashorn JavaScript engine to process data transformations in DataProcessor.java" +- ✅ Good: "ProcessBuilder executes shell scripts for deployment automation in 8 service classes" +- ✅ Good: "No embedded language usage detected - pure Java implementation with standard JDBC" +- ❌ Bad: "Scripting is used" +- ❌ Bad: "Found some code execution" + +**Evidence Examples**: +- ✅ Good: "GroovyShell.evaluate() at src/main/java/com/example/rules/RuleEngine.java:67" +- ✅ Good: "groovy-all-2.5.14 dependency in pom.xml with 23 .groovy files in resources/rules/" +- ✅ Good: "ScriptEngineManager initialized in MainController.java:45 with JavaScript engine type" +- ❌ Bad: "Found scripting code" +- ❌ Bad: "Process execution detected" + +## Error Handling + +### 1. No Evidence Found +- Don't fail - report finding as "No embedded language usage detected" +- Set confidence to high if thorough search performed +- Provide reasoning: "Searched for process execution, script engines, and embedded interpreters - none found" + +### 2. Ambiguous Results +- Report what was found with context +- Set confidence to medium +- Explain uncertainty: "Groovy dependency present but no explicit usage found in code" + +### 3. Tool Failures +- If Grep fails, retry with simpler pattern +- If Grep still fails, try Bash with find/grep combination +- If Read fails on large files, try reading with offset/limit +- After 3 retry attempts, return error status with details + +### 4. Large Codebases +- If initial search returns too many results (>500 matches), refine pattern +- Focus on main source directories (exclude test code initially) +- Prioritize by file path (src/main > src/test) +- Sample representative files for detailed analysis + +## Example Complete Analysis + +**Scenario**: Java Spring Boot application with Groovy rule engine + +**Steps Executed**: +1. Grep for ScriptEngine: Found 3 matches in RuleEngine.java +2. Grep for GroovyShell: Found 15 matches across 5 files +3. Glob for .groovy files: Found 23 files in resources/rules/ +4. Read RuleEngine.java: Confirmed GroovyShell usage with dynamic script loading +5. Read pom.xml: Found groovy-all-2.5.14 dependency + +**Result**: +```json +{ + "input_name": "Embedded Language Usage", + "analysis_method": "LLM", + "status": "success", + "result": { + "finding": "Application uses Groovy as embedded scripting engine for business rules execution with 23 rule scripts loaded dynamically at runtime", + "confidence": "high", + "evidence": [ + "GroovyShell.evaluate() at src/main/java/com/example/rules/RuleEngine.java:67-89", + "groovy-all-2.5.14 dependency in pom.xml line 45", + "23 .groovy script files in src/main/resources/rules/ directory", + "RuleLoader.java:34 loads scripts from classpath using ResourceLoader", + "15 GroovyShell usage instances across 5 files in rules package" + ], + "values": [ + "Groovy 2.5.14 (groovy-all)", + "GroovyShell with CompilerConfiguration", + "23 business rule scripts (.groovy)", + "Dynamic rule evaluation at runtime" + ] + }, + "execution_time_seconds": 38.5, + "timestamp": "2026-02-28T10:15:42Z" +} +``` diff --git a/.github/skills/fact-environment-variables/SKILL.md b/.github/skills/fact-environment-variables/SKILL.md new file mode 100644 index 000000000..2cef10d2a --- /dev/null +++ b/.github/skills/fact-environment-variables/SKILL.md @@ -0,0 +1,105 @@ +--- +name: fact-environment-variables +description: Identify container environment variables and configuration +--- + +# Environment Variables Analysis + +## Purpose +Catalog environment variables used in containerized applications for configuration, secrets, and runtime behavior. + +## Target Files/Locations +- **/Dockerfile (ENV instruction) +- **/docker-compose*.yml (environment section) +- **/k8s/**/*.yaml (env, envFrom) +- **/.env, **/.env.example + +## Example Patterns +- `ENV DATABASE_URL=...` +- `environment: - NODE_ENV=production` +- `env: - name: API_KEY` +- `envFrom: - configMapRef` + +## Analysis Steps + +### 1. Check Dockerfile ENV +``` +Use Grep: "^ENV\\s+" +Files: **/Dockerfile +Extract variable names and default values +``` + +### 2. Analyze docker-compose Environment +``` +Use Read: **/docker-compose*.yml +Look for environment: section +Format: KEY=VALUE or array of strings +``` + +### 3. Check Kubernetes ConfigMaps/Secrets +``` +Use Grep: "env:|envFrom:|configMapRef|secretRef" +Files: **/k8s/**/*.yaml +Context: -B 2 -A 5 +``` + +### 4. Check .env Files +``` +Use Glob: **/.env, **/.env.example, **/.env.template +Use Read to list variable names (mask values) +``` + +### 5. Categorize Variables +``` +Group by type: +- Database: DATABASE_URL, DB_HOST, DB_PASSWORD +- API Keys: API_KEY, SECRET_KEY +- Feature Flags: ENABLE_FEATURE_X +- Runtime: NODE_ENV, LOG_LEVEL, DEBUG +``` + +## Confidence Determination + +### High Confidence +- ✅ Variables explicitly defined in multiple files +- ✅ .env.example provides template +- **Example**: "12 environment variables configured including DATABASE_URL, API_KEY, NODE_ENV" + +### Medium Confidence +- ⚠️ Some variables in code but not all documented +- **Example**: "Environment variables used but no .env.example template" + +### Low Confidence +- ⚠️ Variables referenced but not listed +- **Example**: "Application likely uses env vars but none explicitly configured" + +### Not Applicable +- ❌ No container or hardcoded config +- **Example**: "No environment variable usage detected" + +## Output Format + +```json +{ + "input_name": "Environment Variables", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Variables summary}", + "confidence": "high|medium|low", + "evidence": [ + "{Dockerfile ENV count}", + "{docker-compose environment}", + "{K8s ConfigMap/Secret refs}", + "{.env file presence}" + ], + "values": [ + "{Variable names (masked values)}", + "{Categories: database, api, runtime, etc.}", + "{Count: N variables}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-external-dependencies/SKILL.md b/.github/skills/fact-external-dependencies/SKILL.md new file mode 100644 index 000000000..f62559edb --- /dev/null +++ b/.github/skills/fact-external-dependencies/SKILL.md @@ -0,0 +1,124 @@ +--- +name: fact-external-dependencies +description: Identify external system dependencies (SQL Server, Redis, LDAP, File shares) +--- + +# External Dependencies Analysis + +## Purpose +Catalog external systems and services the application depends on including databases, caches, authentication services, and storage. + +## Target Files/Locations +- **/application.{properties,yml} (connection strings) +- **/docker-compose*.yml (external services) +- **/*.env.example (dependency URLs) +- **/README.md (setup instructions) +- **/*.{java,cs,js,py} (connection code) + +## Example Patterns +- **Databases**: SQL Server, PostgreSQL, MySQL, Oracle, MongoDB +- **Caches**: Redis, Memcached, Elasticsearch +- **Auth**: LDAP, Active Directory, OAuth providers +- **Storage**: S3, Azure Blob, NFS, SMB +- **Queues**: RabbitMQ, Kafka, SQS, Azure Service Bus + +## Analysis Steps + +### 1. Check Application Configuration +``` +Use Grep: "datasource|database|jdbc:|connectionString|redis|ldap|s3|blob" +Files: **/application.{properties,yml}, **/appsettings*.json +Context: -B 1 -A 2 + +Extract: +- spring.datasource.url=jdbc:sqlserver://... +- ConnectionStrings:DefaultConnection +- redis.host +- ldap.url +``` + +### 2. Check docker-compose Services +``` +Use Read: **/docker-compose*.yml +Identify services: +- postgres, mysql, sqlserver +- redis, memcached +- rabbitmq, kafka +- elasticsearch + +Note: external vs internal dependencies +``` + +### 3. Check Environment Variables +``` +Use Read: **/.env.example +Look for: +- DATABASE_URL +- REDIS_URL +- LDAP_SERVER +- S3_BUCKET +- API endpoint URLs +``` + +### 4. Check Documentation +``` +Use Read: **/README.md +Look for: +- Prerequisites section +- External services setup +- Configuration instructions +``` + +### 5. Search Code for Connections +``` +Use Grep: "SqlConnection|MongoClient|RedisClient|LdapContext|S3Client" +Files: **/*.{java,cs,js,py} +Context: -B 2 -A 3 +``` + +## Confidence Determination + +### High Confidence +- ✅ Dependencies in config + docker-compose + docs +- ✅ Connection strings present +- **Example**: "External dependencies: SQL Server 2019, Redis 6, LDAP (Active Directory), Azure Blob Storage from config and docs" + +### Medium Confidence +- ⚠️ Dependencies referenced but details incomplete +- **Example**: "Database required (connection string present) but type/version unclear" + +### Low Confidence +- ⚠️ Possible dependencies inferred from code +- **Example**: "May use Redis based on client library dependency" + +### Not Applicable +- ❌ Fully self-contained application +- **Example**: "Standalone application with embedded database, no external dependencies" + +## Output Format + +```json +{ + "input_name": "External Dependencies", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Dependencies summary}", + "confidence": "high|medium|low", + "evidence": [ + "{Configuration entries}", + "{docker-compose services}", + "{Documentation}", + "{Connection code}" + ], + "values": [ + "{Dependency: SQL Server, Redis, etc.}", + "{Versions if known}", + "{Purpose: database, cache, auth, storage}", + "{Count: N external systems}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-external-services/SKILL.md b/.github/skills/fact-external-services/SKILL.md new file mode 100644 index 000000000..96ec40729 --- /dev/null +++ b/.github/skills/fact-external-services/SKILL.md @@ -0,0 +1,93 @@ +--- +name: fact-external-services +description: Identify external service dependencies (Database, Redis, Message queues) +--- + +# External Services Analysis + +## Purpose +Detect external service dependencies like databases, caches, message queues, and other backend services the application connects to. + +## Target Files/Locations +- **/docker-compose*.yml (services section) +- **/k8s/**/*.yaml (Service, StatefulSet) +- **/application.{properties,yml} +- **/.env, **/.env.example +- **/*.{java,cs,js,py} (connection strings) + +## Example Patterns +- `postgres:13`, `redis:alpine`, `rabbitmq:3-management` +- `spring.datasource.url`, `REDIS_URL`, `MONGODB_URI` + +## Analysis Steps + +### 1. Check docker-compose Services +``` +Use Read: **/docker-compose*.yml +Look for services: beyond the main app (postgres, redis, mongo, rabbitmq, elasticsearch, etc.) +``` + +### 2. Analyze Application Configuration +``` +Use Grep: "datasource|database|redis|mongo|rabbit|kafka|elasticsearch" +Files: **/application.{properties,yml} +Context: -B 1 -A 2 +``` + +### 3. Check Environment Variables +``` +Use Grep: "DATABASE_URL|REDIS_URL|MONGO|RABBITMQ|KAFKA" +Files: **/.env.example, **/Dockerfile, **/k8s/**/*.yaml +``` + +### 4. Search for Connection Strings in Code +``` +Use Grep: "jdbc:|redis://|mongodb://|amqp://" +Files: **/*.{java,cs,js,py} +Context: -B 2 -A 1 +``` + +## Confidence Determination + +### High Confidence +- ✅ Services in docker-compose + connection config +- ✅ Connection strings in application config +- **Example**: "External services: PostgreSQL 13, Redis 6, RabbitMQ 3 from docker-compose and connection strings" + +### Medium Confidence +- ⚠️ References to services but no explicit config +- **Example**: "Database referenced in code but connection details unclear" + +### Low Confidence +- ⚠️ Possible service usage, not confirmed +- **Example**: "May use database based on ORM dependency" + +### Not Applicable +- ❌ Standalone app with no external services +- **Example**: "Static file server, no external dependencies" + +## Output Format + +```json +{ + "input_name": "External Services", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Services summary}", + "confidence": "high|medium|low", + "evidence": [ + "{docker-compose services}", + "{Connection configs}", + "{Environment variables}" + ], + "values": [ + "{Service types: PostgreSQL, Redis, etc.}", + "{Versions}", + "{Count: N services}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-hardware-requirements/SKILL.md b/.github/skills/fact-hardware-requirements/SKILL.md new file mode 100644 index 000000000..b789d5a66 --- /dev/null +++ b/.github/skills/fact-hardware-requirements/SKILL.md @@ -0,0 +1,115 @@ +--- +name: fact-hardware-requirements +description: Identify minimum hardware requirements (RAM, CPU, disk) +--- + +# Hardware Requirements Analysis + +## Purpose +Extract minimum hardware requirements from documentation, resource configurations, and deployment files. + +## Target Files/Locations +- **/README.md, **/INSTALL.md, **/REQUIREMENTS.md (system requirements) +- **/docs/**/*.md (hardware mentions) +- **/docker-compose*.yml (resource limits) +- **/k8s/**/*.yaml (resource requests/limits) + +## Example Patterns +- "4GB RAM minimum" +- "2 CPU cores recommended" +- "10GB disk space required" +- `limits: memory: "4Gi", cpu: "2"` + +## Analysis Steps + +### 1. Check Documentation +``` +Use Read: **/README.md, **/INSTALL.md, **/REQUIREMENTS.md +Search for: +- "System Requirements" section +- "Hardware Requirements" section +- RAM/memory mentions (GB, GiB) +- CPU mentions (cores, GHz) +- Disk mentions (GB storage) + +Use Grep: "[0-9]+\\s*(GB|GiB|MB|MiB)|[0-9]+\\s*(cores?|CPU)|disk|storage" +Context: -B 2 -A 2 +``` + +### 2. Check Container Resource Limits +``` +Use Read: **/docker-compose*.yml +Look for deploy.resources.limits/reservations + +Use Grep: "memory:|cpu:" +Files: **/k8s/**/*.yaml +Context: -B 3 -A 1 + +Extract resource specifications: +- Memory: 2Gi, 4Gi, 512Mi +- CPU: 1000m, 2, 500m +``` + +### 3. Analyze Resource Patterns +``` +From K8s/Compose: +- limits = maximum resources +- requests/reservations = minimum required + +Calculate totals for multi-container apps +``` + +### 4. Check Database Requirements +``` +If database used, estimate: +- PostgreSQL: ~1GB RAM minimum +- MySQL: ~512MB RAM minimum +- MongoDB: ~1GB RAM minimum +- Plus storage for data +``` + +## Confidence Determination + +### High Confidence +- ✅ Requirements explicitly documented +- ✅ Resource limits configured match docs +- **Example**: "4GB RAM, 2 CPU cores, 10GB disk from README and matching K8s resource requests" + +### Medium Confidence +- ⚠️ Requirements in one source only +- **Example**: "4GB memory limit in docker-compose, no documentation" + +### Low Confidence +- ⚠️ Requirements estimated from resources used +- **Example**: "Estimated 2GB RAM based on container limits, not documented" + +### Not Applicable +- ❌ Serverless or managed platform +- **Example**: "AWS Lambda deployment, hardware not directly specified" + +## Output Format + +```json +{ + "input_name": "Hardware Requirements", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Hardware summary}", + "confidence": "high|medium|low", + "evidence": [ + "{Documentation sections}", + "{Container resource limits}", + "{Calculations}" + ], + "values": [ + "{RAM: minimum and recommended}", + "{CPU: cores or millicores}", + "{Disk: storage requirements}", + "{Network: if specified}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-health-checks/SKILL.md b/.github/skills/fact-health-checks/SKILL.md new file mode 100644 index 000000000..4c12bd2bf --- /dev/null +++ b/.github/skills/fact-health-checks/SKILL.md @@ -0,0 +1,110 @@ +--- +name: fact-health-checks +description: Identify health check configurations (HTTP checks, command checks) +--- + +# Health Checks Analysis + +## Purpose +Identify health check and readiness probe configurations for monitoring application health. + +## Target Files/Locations +- **/Dockerfile (HEALTHCHECK instruction) +- **/docker-compose*.yml (healthcheck section) +- **/k8s/**/*.yaml (livenessProbe, readinessProbe, startupProbe) +- **/*.{java,cs,js,py,go} (health endpoints) + +## Example Patterns +- `HEALTHCHECK CMD curl -f http://localhost/ || exit 1` +- `healthcheck: test: ["CMD", "curl", "-f", "http://localhost"]` +- `livenessProbe: httpGet: path: /health` + +## Analysis Steps + +### 1. Check Dockerfile HEALTHCHECK +``` +Use Grep: "^HEALTHCHECK" +Files: **/Dockerfile +Parse: CMD, interval, timeout, retries +``` + +### 2. Analyze docker-compose Healthchecks +``` +Use Read: **/docker-compose*.yml +Look for healthcheck: section per service + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/health"] + interval: 30s + timeout: 10s + retries: 3 +``` + +### 3. Check Kubernetes Probes +``` +Use Grep: "livenessProbe:|readinessProbe:|startupProbe:" +Files: **/k8s/**/*.yaml +Context: -A 10 + +Parse probe types: +- httpGet: path + port +- exec: command +- tcpSocket: port +``` + +### 4. Search for Health Endpoints in Code +``` +Use Grep: "/health|/healthz|/ready|/live|HealthCheck" +Files: **/*.{java,cs,js,py,go} +Context: -B 2 -A 5 + +Common frameworks: +- Spring Boot: /actuator/health +- ASP.NET Core: /health +- Express.js: /health +``` + +## Confidence Determination + +### High Confidence +- ✅ Health checks configured in orchestration files +- ✅ Health endpoint exists in code +- **Example**: "Health checks: HTTP GET /health endpoint with 30s interval, 3 retries" + +### Medium Confidence +- ⚠️ Health check configured but endpoint unclear +- **Example**: "HEALTHCHECK defined using curl but endpoint not verified" + +### Low Confidence +- ⚠️ No explicit health checks configured +- **Example**: "No health checks configured, relies on container exit codes" + +### Not Applicable +- ❌ Simple app with no health monitoring needs +- **Example**: "Batch job, health checks not applicable" + +## Output Format + +```json +{ + "input_name": "Health Checks", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Health checks summary}", + "confidence": "high|medium|low", + "evidence": [ + "{Dockerfile HEALTHCHECK}", + "{docker-compose healthcheck}", + "{K8s probes}", + "{Health endpoint in code}" + ], + "values": [ + "{Check type: HTTP, exec, TCP}", + "{Endpoint: /health, /ready, etc.}", + "{Timing: interval, timeout, retries}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-image-layers/SKILL.md b/.github/skills/fact-image-layers/SKILL.md new file mode 100644 index 000000000..ec7e63aa8 --- /dev/null +++ b/.github/skills/fact-image-layers/SKILL.md @@ -0,0 +1,102 @@ +--- +name: fact-image-layers +description: Analyze container image layer count and structure +--- + +# Image Layers Analysis + +## Purpose +Analyze Dockerfile instructions to understand image layer structure, count of layers, and optimization opportunities. + +## Target Files/Locations +- **/Dockerfile, **/Containerfile + +## Example Patterns +Layer-creating instructions: RUN, COPY, ADD, FROM +Non-layer instructions: ENV, ARG, LABEL, EXPOSE, CMD, ENTRYPOINT, WORKDIR + +## Analysis Steps + +### 1. Count Layer-Creating Instructions +``` +Use Grep to count RUN/COPY/ADD: +- Pattern: "^(RUN|COPY|ADD)\\s+" +- Files: **/Dockerfile, **/Containerfile +- Mode: count + +Each match creates a layer (except multi-stage FROM) +``` + +### 2. Analyze RUN Instruction Complexity +``` +Use Read Dockerfile and analyze: +- Single-command RUN vs multi-command (&&) +- Layer optimization (combined commands) +- Example: RUN apt-get update && apt-get install (1 layer) + vs: RUN apt-get update \n RUN apt-get install (2 layers) +``` + +### 3. Check for Multi-stage Build +``` +Count FROM instructions: +Each stage adds base layers +Final image only includes layers from last stage + COPY --from +``` + +### 4. Identify Layer Size Contributors +``` +Look for large operations: +- Package installations (apt-get, yum, apk) +- File copies (COPY large directories) +- Downloads (wget, curl in RUN) +``` + +## Confidence Determination + +### High Confidence +- ✅ Dockerfile analyzed completely +- ✅ Clear count of layer instructions +- ✅ Multi-stage structure understood +- **Example**: "Image has 8 layers: 3 from base (alpine), 3 RUN, 2 COPY" + +### Medium Confidence +- ⚠️ Some build-time variables affect layers +- ⚠️ External build process unclear +- **Example**: "Approximately 6-8 layers based on Dockerfile instructions" + +### Low Confidence +- ⚠️ Dockerfile uses complex ARGs affecting structure +- ⚠️ BuildKit features that modify layering +- **Example**: "Layer count unclear due to conditional build steps" + +### Not Applicable +- ❌ No Dockerfile +- **Example**: "No container image definition found" + +## Output Format + +```json +{ + "input_name": "Image Layers", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Layer analysis summary}", + "confidence": "high|medium|low", + "evidence": [ + "{RUN instruction count}", + "{COPY/ADD instruction count}", + "{Multi-stage stages}", + "{Optimization patterns observed}" + ], + "values": [ + "{Estimated layer count}", + "{FROM instructions: N}", + "{RUN instructions: N}", + "{COPY/ADD instructions: N}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-image-size/SKILL.md b/.github/skills/fact-image-size/SKILL.md new file mode 100644 index 000000000..64771d061 --- /dev/null +++ b/.github/skills/fact-image-size/SKILL.md @@ -0,0 +1,121 @@ +--- +name: fact-image-size +description: Analyze and estimate container image size +--- + +# Image Size Analysis + +## Purpose +Estimate container image size based on base image, installed packages, and copied files. Identify size optimization opportunities. + +## Target Files/Locations +- **/Dockerfile, **/Containerfile +- Application artifacts to be copied + +## Example Patterns +- Base image (alpine: ~5MB, ubuntu: ~70MB, node:16: ~900MB) +- Package installations (apt, yum, apk) +- Application files (JAR, WAR, binaries) + +## Analysis Steps + +### 1. Identify Base Image Size +``` +Read Dockerfile for FROM instruction +Estimate base sizes: +- scratch: 0MB +- alpine:3.15: ~5MB +- distroless: ~20MB +- ubuntu:20.04: ~70MB +- debian:11: ~120MB +- node:16: ~900MB +- openjdk:11: ~600MB +- mcr.microsoft.com/dotnet/runtime:6.0: ~180MB +``` + +### 2. Analyze Package Installations +``` +Use Grep: "apt-get install|apk add|yum install" +Files: **/Dockerfile +Context: -A 3 + +Estimate: +- Few utilities (curl, wget): +5-10MB +- Build tools (gcc, make): +100-200MB +- Large packages (nginx, postgresql): +50-100MB each +``` + +### 3. Check Application Files Size +``` +For COPY/ADD instructions: +- Look for source paths +- Use Bash to check size: du -sh {source_path} +- Common sizes: + - JAR files: 30-150MB + - Node modules: 100-500MB + - .NET publish: 50-100MB +``` + +### 4. Consider Multi-stage Build Efficiency +``` +If multi-stage: +- Build stage size doesn't matter +- Only final stage COPY --from adds to size +- Final image = base + runtime files only +``` + +### 5. Calculate Estimate +``` +Total = Base + Packages + Application Files + Layer Overhead +Layer overhead: ~10-20% for metadata +``` + +## Confidence Determination + +### High Confidence +- ✅ Base image size known +- ✅ Application files sized via filesystem +- ✅ Package installations enumerated +- **Example**: "Estimated 250MB: alpine (5MB) + Java runtime (150MB) + app JAR (85MB) + dependencies (10MB)" + +### Medium Confidence +- ⚠️ Base image known but packages unclear +- ⚠️ Application size estimated without measurement +- **Example**: "Approximately 200-300MB based on Node.js base and typical app size" + +### Low Confidence +- ⚠️ Complex build process with many layers +- ⚠️ Cannot access application files for sizing +- **Example**: "Likely 100MB-1GB range, difficult to estimate without build" + +### Not Applicable +- ❌ No containerization +- **Example**: "No Dockerfile found" + +## Output Format + +```json +{ + "input_name": "Image Size", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Image size estimate with breakdown}", + "confidence": "high|medium|low", + "evidence": [ + "{Base image and size}", + "{Package installations}", + "{Application files size}", + "{Calculation method}" + ], + "values": [ + "{Estimated total size: XMB or X.XGB}", + "{Base image size}", + "{Application layer size}", + "{Dependencies size}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-language-dependencies/SKILL.md b/.github/skills/fact-language-dependencies/SKILL.md new file mode 100644 index 000000000..eefd957c2 --- /dev/null +++ b/.github/skills/fact-language-dependencies/SKILL.md @@ -0,0 +1,104 @@ +--- +name: fact-language-dependencies +description: Identify language-specific dependencies in container (package.json, requirements.txt, pom.xml) +--- + +# Language Dependencies Analysis + +## Purpose +Identify application-level dependencies through package manifests copied into the container image. + +## Target Files/Locations +- **/package.json, **/package-lock.json (Node.js) +- **/requirements.txt, **/Pipfile (Python) +- **/pom.xml, **/build.gradle (Java) +- **/*.csproj, **/packages.config (.NET) +- **/go.mod, **/go.sum (Go) +- **/Gemfile, **/Gemfile.lock (Ruby) +- **/composer.json (PHP) + +## Example Patterns +- `COPY package*.json ./` +- `RUN pip install -r requirements.txt` +- `RUN npm install --production` +- `RUN mvn clean install` + +## Analysis Steps + +### 1. Identify Copied Dependency Files +``` +Use Grep: "COPY.*(package\\.json|requirements\\.txt|pom\\.xml|.*\\.csproj|go\\.mod|Gemfile|composer\\.json)" +Files: **/Dockerfile +Context: -B 1 -A 1 +``` + +### 2. Read Dependency Files +``` +Use Glob to find dependency files in project: +- **/package.json (read dependencies section) +- **/requirements.txt (read package list) +- **/pom.xml (read <dependencies>) +- **/*.csproj (read <PackageReference>) +``` + +### 3. Count and Categorize Dependencies +``` +For each runtime: +- Node.js: dependencies vs devDependencies +- Python: required packages +- Java: compile, runtime, test scope +- .NET: PackageReference items +- Go: direct vs indirect +``` + +### 4. Check Install Commands +``` +Use Grep: "npm install|pip install|mvn|gradle|dotnet restore|go mod download" +Files: **/Dockerfile +Verify dependencies are installed in image +``` + +## Confidence Determination + +### High Confidence +- ✅ Dependency files copied and installed in Dockerfile +- ✅ Files readable and parseable +- **Example**: "45 Node.js dependencies from package.json installed via npm install" + +### Medium Confidence +- ⚠️ Dependency files exist but install command unclear +- **Example**: "package.json present, installation method not explicit" + +### Low Confidence +- ⚠️ Can't access dependency files +- **Example**: "Dependency files referenced but not readable" + +### Not Applicable +- ❌ No language dependencies (native binary) +- **Example**: "Compiled Go binary with no external dependencies" + +## Output Format + +```json +{ + "input_name": "Language Dependencies", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Dependencies summary}", + "confidence": "high|medium|low", + "evidence": [ + "{Dependency files found}", + "{COPY/install commands}", + "{Dependency count}" + ], + "values": [ + "{Dependency file: package.json, requirements.txt, etc.}", + "{Dependency count}", + "{Key dependencies list}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-licensing-information/SKILL.md b/.github/skills/fact-licensing-information/SKILL.md new file mode 100644 index 000000000..f41f10ea0 --- /dev/null +++ b/.github/skills/fact-licensing-information/SKILL.md @@ -0,0 +1,119 @@ +--- +name: fact-licensing-information +description: Identify software licensing details from LICENSE files and dependency analysis +--- + +# Licensing Information Analysis + +## Purpose +Extract licensing information for the application and its dependencies to understand compliance requirements. + +## Target Files/Locations +- **/LICENSE, **/LICENSE.txt, **/LICENSE.md +- **/pom.xml (<licenses>) +- **/package.json (license field) +- **/*.csproj (<PackageLicenseExpression>) +- **/NOTICE, **/COPYING, **/COPYRIGHT + +## Example Patterns +- MIT License +- Apache License 2.0 +- GPL v3, LGPL +- BSD 3-Clause +- Commercial/Proprietary + +## Analysis Steps + +### 1. Check License Files +``` +Use Glob: **/LICENSE*, **/COPYING, **/COPYRIGHT +Use Read to examine content + +Identify license type: +- MIT: "Permission is hereby granted, free of charge" +- Apache 2.0: "Apache License, Version 2.0" +- GPL: "GNU General Public License" +- BSD: "Redistribution and use in source and binary forms" +``` + +### 2. Check Build File Licenses +``` +Maven (pom.xml): +Use Grep: "<licenses>|<license>" +Extract: <name>Apache License 2.0</name> + +Node.js (package.json): +Use Read and parse: { "license": "MIT" } + +.NET (*.csproj): +Use Grep: "<PackageLicenseExpression>" +Extract: <PackageLicenseExpression>MIT</PackageLicenseExpression> +``` + +### 3. Check Dependency Licenses +``` +Look for license reports: +- **/license-report.html +- **/licenses/ directory +- THIRD-PARTY-NOTICES + +Tools that generate these: +- maven-license-plugin +- license-checker (npm) +- dotnet list package --include-transitive +``` + +### 4. Scan for Commercial Licenses +``` +Use Grep: "commercial|proprietary|all rights reserved|confidential" +Files: **/LICENSE*, **/README.md +Context: -B 2 -A 2 +``` + +## Confidence Determination + +### High Confidence +- ✅ LICENSE file present with clear text +- ✅ License in build file matches LICENSE file +- **Example**: "MIT License confirmed in LICENSE file and package.json" + +### Medium Confidence +- ⚠️ License file present but type unclear +- ⚠️ Multiple licenses mentioned +- **Example**: "LICENSE file present, appears to be MIT but not standard format" + +### Low Confidence +- ⚠️ No LICENSE file, inferred from comments +- **Example**: "No LICENSE file, copyright header suggests proprietary" + +### Not Applicable +- ❌ Internal tool with no license requirement +- **Example**: "Internal company tool, no formal licensing" + +## Output Format + +```json +{ + "input_name": "Licensing Information", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{License summary}", + "confidence": "high|medium|low", + "evidence": [ + "{LICENSE file presence and content}", + "{Build file license declaration}", + "{Dependency licenses if available}", + "{Copyright notices}" + ], + "values": [ + "{Primary license: MIT, Apache 2.0, etc.}", + "{Dependency licenses if scanned}", + "{Copyright holder}", + "{License compatibility notes}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-multi-stage-build/SKILL.md b/.github/skills/fact-multi-stage-build/SKILL.md new file mode 100644 index 000000000..dde08a83c --- /dev/null +++ b/.github/skills/fact-multi-stage-build/SKILL.md @@ -0,0 +1,92 @@ +--- +name: fact-multi-stage-build +description: Check if Dockerfile uses multi-stage build pattern +--- + +# Multi-stage Build Analysis + +## Purpose +Determine if the project uses Docker multi-stage builds for image optimization. + +## Target Files/Locations +- **/Dockerfile, **/Dockerfile.*, **/*.Dockerfile, **/Containerfile + +## Analysis Steps + +### 1. Find Dockerfile(s) +``` +Use Glob: **/Dockerfile, **/Dockerfile.*, **/*.Dockerfile, **/Containerfile +List all matching files +``` + +### 2. Count FROM Instructions +``` +Use Grep: "^FROM\\s+" +Files: (all Dockerfiles found in step 1) + +Count the number of FROM instructions per file: +- 1 FROM = single-stage build +- 2+ FROM = multi-stage build +``` + +### 3. Extract Named Stages +``` +Use Grep: "^FROM\\s+.*\\s+[Aa][Ss]\\s+" +Files: (all Dockerfiles found in step 1) +Context: full line + +Extract stage names from "FROM ... AS stage_name" patterns +``` + +### 4. Verify Stage Usage +``` +Use Grep: "--from=" +Files: (all Dockerfiles found in step 1) + +Check COPY --from=stage_name instructions to verify multi-stage usage +``` + +## Confidence Determination + +### High Confidence +- ✅ Multiple FROM instructions with named stages +- ✅ COPY --from= instructions present +- **Example**: "Multi-stage build detected in Dockerfile with 3 stages (builder, tester, runtime)" + +### Medium Confidence +- ⚠️ Multiple FROM instructions but no named stages +- **Example**: "Multi-stage build with 2 unnamed stages" + +### Low Confidence +- ⚠️ Single FROM instruction +- **Example**: "Single-stage build, no optimization" + +### Not Applicable +- ❌ No Dockerfile found +- **Example**: "No Dockerfile or Containerfile found in project" + +## Output Format + +```json +{ + "input_name": "Multi-stage Build", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Multi-stage build summary}", + "confidence": "high|medium|low", + "evidence": [ + "{Dockerfile locations}", + "{FROM instruction count}", + "{Named stages}" + ], + "values": [ + "{Build type: Multi-stage or Single-stage}", + "{Stage count}", + "{Stage names if available}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-network-settings/SKILL.md b/.github/skills/fact-network-settings/SKILL.md new file mode 100644 index 000000000..268fb17de --- /dev/null +++ b/.github/skills/fact-network-settings/SKILL.md @@ -0,0 +1,90 @@ +--- +name: fact-network-settings +description: Analyze container network configuration (bridge, host, custom networks) +--- + +# Network Settings Analysis + +## Purpose +Identify container networking configuration including network modes, custom networks, and inter-service communication. + +## Target Files/Locations +- **/docker-compose*.yml (networks section) +- **/k8s/**/*.yaml (Service, NetworkPolicy) +- **/Dockerfile (EXPOSE) + +## Example Patterns +- `networks: - frontend - backend` +- `network_mode: bridge|host|none` +- `kind: NetworkPolicy` + +## Analysis Steps + +### 1. Check docker-compose Networks +``` +Use Read: **/docker-compose*.yml +Look for: +- networks: top-level definitions +- service-level network assignments +- network_mode: bridge, host, none, container:name +``` + +### 2. Analyze Kubernetes Networking +``` +Use Grep: "kind: Service|kind: NetworkPolicy|clusterIP|nodePort" +Files: **/k8s/**/*.yaml +Context: -B 1 -A 10 +``` + +### 3. Check for Custom Network Drivers +``` +In docker-compose: +- driver: bridge, overlay, macvlan +- ipam: configuration +- external: true/false +``` + +## Confidence Determination + +### High Confidence +- ✅ Networks explicitly configured +- ✅ Network mode and drivers specified +- **Example**: "Custom bridge network 'app-network' with frontend and backend subnets" + +### Medium Confidence +- ⚠️ Default networking, no custom config +- **Example**: "Uses default bridge network, no custom configuration" + +### Low Confidence +- ⚠️ Networking inferred from service definitions +- **Example**: "Networking likely default, no explicit configuration" + +### Not Applicable +- ❌ Non-networked application +- **Example**: "CLI tool, no network requirements" + +## Output Format + +```json +{ + "input_name": "Network Settings", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Networking summary}", + "confidence": "high|medium|low", + "evidence": [ + "{docker-compose networks}", + "{K8s Services/Policies}", + "{Network mode}" + ], + "values": [ + "{Network names}", + "{Network mode: bridge, host, overlay}", + "{Driver: bridge, overlay, etc.}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-operating-system/SKILL.md b/.github/skills/fact-operating-system/SKILL.md new file mode 100644 index 000000000..57a37dc44 --- /dev/null +++ b/.github/skills/fact-operating-system/SKILL.md @@ -0,0 +1,126 @@ +--- +name: fact-operating-system +description: Identify target/current operating system for deployment +--- + +# Operating System Analysis + +## Purpose +Determine the target or current operating system the application runs on, especially for containerized or cloud-native applications. + +## Target Files/Locations +- **/Dockerfile (FROM base image OS) +- **/README.md, **/docs/**/*.md (system requirements) +- **/k8s/**/*.yaml (nodeSelector, node affinity) +- **/pom.xml, **/*.csproj (target runtime identifiers) + +## Example Patterns +- **Container base**: FROM alpine:3.15, FROM ubuntu:20.04, FROM mcr.microsoft.com/windows/servercore:ltsc2019 +- **.NET RID**: linux-x64, win-x64, osx-x64 +- **Node selector**: kubernetes.io/os: linux + +## Analysis Steps + +### 1. Check Container Base Image OS +``` +Use Grep: "^FROM\\s+" +Files: **/Dockerfile +Extract base image name + +Map to OS: +- alpine → Alpine Linux +- ubuntu → Ubuntu Linux +- debian → Debian Linux +- centos/rocky → CentOS/Rocky Linux +- windows/servercore → Windows Server +- mcr.microsoft.com/windows → Windows +``` + +### 2. Check .NET Runtime Identifiers +``` +Use Grep: "RuntimeIdentifier|<RuntimeIdentifiers>" +Files: **/*.csproj + +RIDs: +- linux-x64 → Linux 64-bit +- linux-arm64 → Linux ARM 64-bit +- win-x64 → Windows 64-bit +- win-arm64 → Windows ARM 64-bit +- osx-x64 → macOS Intel +- osx-arm64 → macOS Apple Silicon +``` + +### 3. Check Kubernetes Node Selectors +``` +Use Grep: "nodeSelector:|kubernetes\\.io/os:" +Files: **/k8s/**/*.yaml +Context: -A 3 + +Values: +- linux → Linux nodes +- windows → Windows nodes +``` + +### 4. Check Documentation +``` +Use Read: **/README.md (first 100 lines) +Look for: +- System Requirements section +- OS mentions (Linux, Windows, macOS) +- Installation instructions per OS +``` + +### 5. Check Platform-Specific Scripts +``` +Use Glob: +- **/*.sh → Linux/macOS +- **/*.ps1, **/*.bat → Windows + +Presence indicates OS support +``` + +## Confidence Determination + +### High Confidence +- ✅ OS explicitly defined in container base or RID +- ✅ Documentation confirms OS +- **Example**: "Target OS: Alpine Linux 3.15 from Dockerfile base image" + +### Medium Confidence +- ⚠️ OS inferred from framework or scripts +- **Example**: "Likely Linux (shell scripts present, typical Java deployment)" + +### Low Confidence +- ⚠️ OS unclear, multiple possibilities +- **Example**: "Cross-platform framework, specific OS deployment unclear" + +### Not Applicable +- ❌ Pure Java/JVM with no OS-specific features +- **Example**: "Platform-independent Java library" + +## Output Format + +```json +{ + "input_name": "Operating System", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{OS summary}", + "confidence": "high|medium|low", + "evidence": [ + "{Container base image}", + "{Runtime identifier}", + "{Documentation}", + "{Scripts}" + ], + "values": [ + "{OS: Alpine Linux, Ubuntu, Windows Server, etc.}", + "{Version: 3.15, 20.04, 2019, etc.}", + "{Architecture: x64, arm64}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-orchestration-tool/SKILL.md b/.github/skills/fact-orchestration-tool/SKILL.md new file mode 100644 index 000000000..00fabf2a4 --- /dev/null +++ b/.github/skills/fact-orchestration-tool/SKILL.md @@ -0,0 +1,96 @@ +--- +name: fact-orchestration-tool +description: Identify container orchestration platform (Docker Compose, Kubernetes, Docker Swarm) +--- + +# Orchestration Tool Analysis + +## Purpose +Determine which container orchestration tool manages the application deployment. + +## Target Files/Locations +- **/docker-compose*.yml (Docker Compose) +- **/k8s/**/*.yaml, **/*.yaml (Kubernetes) +- **/swarm/ (Docker Swarm) +- **/.github/workflows/, **/.gitlab-ci.yml (CI/CD hints) + +## Example Patterns +- Docker Compose: `docker-compose.yml`, `version: '3.8'` +- Kubernetes: `kind: Deployment`, `apiVersion: apps/v1` +- Docker Swarm: `deploy: mode: replicated` + +## Analysis Steps + +### 1. Check for Docker Compose +``` +Use Glob: **/docker-compose*.yml +If found: Docker Compose is used +Check version: '2.x' or '3.x' +``` + +### 2. Check for Kubernetes Manifests +``` +Use Glob: **/k8s/**/*.yaml, **/manifests/**/*.yaml +Use Grep: "apiVersion:.*apps/v1|kind: Deployment|kind: Service" +Files: **/*.yaml +If Kubernetes resources found: K8s is used +``` + +### 3. Check for Docker Swarm Config +``` +Use Grep: "deploy:|mode: replicated|placement:" +Files: **/docker-compose*.yml +Swarm uses Compose format with deploy: section +``` + +### 4. Check CI/CD for Deployment Hints +``` +Use Grep: "kubectl|helm|docker-compose|docker stack" +Files: **/.github/workflows/*.yml, **/.gitlab-ci.yml +Context: -B 2 -A 2 +``` + +## Confidence Determination + +### High Confidence +- ✅ Clear orchestration files present +- ✅ CI/CD deploys using specific tool +- **Example**: "Kubernetes orchestration with 15 manifests in k8s/ directory and kubectl in CI/CD" + +### Medium Confidence +- ⚠️ Files present but usage unclear +- **Example**: "docker-compose.yml for local dev, K8s for production (multiple tools)" + +### Low Confidence +- ⚠️ No clear orchestration indicators +- **Example**: "Single Dockerfile, orchestration unclear" + +### Not Applicable +- ❌ No container orchestration +- **Example**: "Direct Docker run, no orchestration" + +## Output Format + +```json +{ + "input_name": "Orchestration Tool", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Orchestration summary}", + "confidence": "high|medium|low", + "evidence": [ + "{docker-compose.yml presence}", + "{K8s manifest count and location}", + "{CI/CD deployment commands}" + ], + "values": [ + "{Tool: Docker Compose, Kubernetes, Docker Swarm}", + "{Version: Compose 3.8, K8s 1.25, etc.}", + "{Manifest count}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-profile-settings/SKILL.md b/.github/skills/fact-profile-settings/SKILL.md new file mode 100644 index 000000000..9112a6b57 --- /dev/null +++ b/.github/skills/fact-profile-settings/SKILL.md @@ -0,0 +1,332 @@ +--- +name: fact-profile-settings +description: Analyze environment-specific profiles and configuration +--- + +# Profile Settings Analysis + +## Purpose +Identify how the application manages environment-specific configurations (Development, Test, Staging, Production) through profiles, property files, or environment-based settings. This helps understand configuration management complexity and deployment requirements. + +## Target Files/Locations +- **Spring profiles**: **/application-{profile}.{properties,yml}, **/application.{properties,yml} +- **Maven profiles**: **/pom.xml (`<profiles>` section) +- **Gradle profiles**: **/build.gradle, **/build.gradle.kts (buildTypes, productFlavors) +- **Environment configs**: **/config/**, **/environments/**, **/profiles/ +- **Java properties**: **/{env}/*.properties, **/*-{env}.properties +- **.NET configs**: **/appsettings.{Environment}.json, **/web.{config}.config +- **Docker/K8s**: **/docker-compose.{env}.yml, **/k8s/**/*-{env}.yaml +- **CI/CD**: **/.github/workflows/, **/.gitlab-ci.yml, **/Jenkinsfile + +## Example Patterns to Search +- **Spring profiles**: `spring.profiles.active`, `@Profile("dev")`, `application-dev.properties` +- **Maven profiles**: `<profile><id>development</id>`, `<activeByDefault>`, `<activation>` +- **Environment variables**: `${ENV:prod}`, `#{environment}`, `process.env.NODE_ENV` +- **Profile-specific beans**: `@Profile("production")`, `@ConditionalOnProfile` +- **Config file names**: dev, test, staging, uat, prod, production, development + +## Analysis Steps + +### 1. Search for Spring Profile Configuration Files +``` +Use Glob to find Spring profile-specific configs: +- **/application-dev.{properties,yml,yaml} +- **/application-test.{properties,yml,yaml} +- **/application-staging.{properties,yml,yaml} +- **/application-prod.{properties,yml,yaml} +- **/application-production.{properties,yml,yaml} +- **/application-uat.{properties,yml,yaml} +- **/application-local.{properties,yml,yaml} + +Use Read to examine application.properties/yml: +- Check for spring.profiles.active setting +- Look for profile-specific property groups (--- separator in YAML) +- Identify default profile configuration + +Count and categorize profiles found +``` + +### 2. Search for @Profile Annotations in Java Code +``` +Use Grep to find profile usage in code: +Pattern: "@Profile\\(|@ConditionalOnProfile|spring\\.profiles\\." +Files: **/*.java +Context: -B 1 -A 2 + +Analyze: +- Which beans/components are profile-specific +- Profile names used in annotations +- Conditional logic based on profiles +``` + +### 3. Analyze Maven Profile Configuration +``` +Use Glob to find Maven build file: +- **/pom.xml + +Use Read or Grep to search for: +- <profiles> section +- <profile><id> elements (dev, test, prod, etc.) +- <activeByDefault> settings +- <activation> conditions (property, JDK, OS) +- Profile-specific properties, dependencies, plugins + +Example patterns to search: +<profile> + <id>development</id> + <activation> + <activeByDefault>true</activeByDefault> + </activation> +</profile> +``` + +### 4. Check Gradle Build Profiles +``` +Use Glob to find Gradle files: +- **/build.gradle +- **/build.gradle.kts + +Use Read to check for: +- buildTypes { debug, release } +- productFlavors { dev, staging, prod } +- Environment-specific configurations +- ext { profile = project.hasProperty('env') ? env : 'dev' } +``` + +### 5. Search for .NET Environment Configurations +``` +Use Glob to find .NET configs: +- **/appsettings.Development.json +- **/appsettings.Staging.json +- **/appsettings.Production.json +- **/web.Development.config +- **/web.Release.config + +Use Grep in .csproj files: +Pattern: "<Environments>|ASPNETCORE_ENVIRONMENT|IHostEnvironment" +``` + +### 6. Check for Environment-Specific Directories +``` +Use Glob to find environment directories: +- **/config/dev/** +- **/config/prod/** +- **/environments/development/** +- **/profiles/** + +Use Bash to list directory structure: +find . -type d -name "dev" -o -name "test" -o -name "prod" -o -name "staging" | head -20 +``` + +### 7. Analyze Docker/K8s Environment Configurations +``` +Use Glob to find container configs: +- **/docker-compose.dev.yml +- **/docker-compose.prod.yml +- **/k8s/dev/**/*.yaml +- **/k8s/production/**/*.yaml +- **/Dockerfile.dev, **/Dockerfile.prod + +Use Grep to find environment variables: +Pattern: "ENV|ENVIRONMENT|PROFILE" +Files: **/Dockerfile, **/docker-compose*.yml +``` + +### 8. Search for CI/CD Environment Configurations +``` +Use Glob to find CI/CD files: +- **/.github/workflows/**/*.yml +- **/.gitlab-ci.yml +- **/Jenkinsfile +- **/azure-pipelines.yml + +Use Read to check for: +- Environment-based job definitions +- Deployment stages (dev, test, prod) +- Environment-specific variables +``` + +### 9. Count and Categorize Profiles +``` +Aggregate all findings: +- List all profile names found +- Count config files per profile +- Identify primary profiles (dev, test, prod) +- Note any custom or unusual profile names +- Check for profile activation logic +``` + +## Confidence Determination + +### High Confidence Criteria +Clear and comprehensive profile configuration: +- ✅ Multiple profile-specific config files found (dev, test, prod) +- ✅ Profile activation mechanism clearly defined +- ✅ Profile-specific beans or components in code +- ✅ Build tool profiles configured (Maven/Gradle) +- ✅ Consistent naming across different config types +- ✅ Environment-specific properties well documented + +**Examples**: +- "Application uses 4 Spring profiles (dev, test, staging, prod) with separate application-{profile}.yml files and @Profile annotations in 12 configuration classes" +- "Maven build configured with 3 profiles (development, testing, production) using different database connections and feature flags per environment" +- ".NET application with appsettings.{Environment}.json for Development, Staging, and Production with ASPNETCORE_ENVIRONMENT detection" + +### Medium Confidence Criteria +Partial profile configuration or unclear structure: +- ⚠️ Some profile files found but incomplete coverage +- ⚠️ Profiles defined but no clear activation mechanism +- ⚠️ Mixed approaches (some Spring, some environment variables) +- ⚠️ Profile names inconsistent across config types +- ⚠️ Only build profiles without runtime profiles (or vice versa) + +**Examples**: +- "Spring profiles for dev and prod found, but test/staging configs missing" +- "Maven profiles defined but no corresponding Spring profile configs" +- "Environment-based configuration exists but profile names vary (dev vs development, prod vs production)" + +### Low Confidence Criteria +Weak or minimal profile evidence: +- ⚠️ Only default configuration, no environment variants +- ⚠️ Profile files exist but appear unused or outdated +- ⚠️ Single-environment application (dev only) +- ⚠️ Hardcoded values instead of profile-based configs +- ⚠️ Profile references in comments but no implementation + +**Examples**: +- "Only application.properties found, no profile-specific configs" +- "Profile files exist but timestamps suggest not used in 2+ years" +- "Comments mention dev/prod configs but actual implementation uses hardcoded values" + +### Not Applicable Criteria +When profile analysis doesn't apply: +- ❌ Simple utility/library with no environment differences +- ❌ Single-purpose tool with no configuration needs +- ❌ Prototype or demo application +- ❌ Different platform with different config approach +- ❌ Configuration managed entirely external (ConfigMap, external config server) + +**Examples**: +- "Command-line utility with no environment-specific behavior" +- "Demo application with hardcoded sample data" +- "Library project with no deployment configuration" + +## Output Format + +**CRITICAL**: Use the `write_assessment_result` tool (not just output JSON text). + +```json +{ + "input_name": "Profile Settings", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Clear 1-2 sentence summary of profile configuration}", + "confidence": "high|medium|low", + "evidence": [ + "{Number and names of profiles identified}", + "{Profile activation mechanism}", + "{Config files per profile}", + "{Code using profile-specific logic}", + "{Build tool profile configuration}" + ], + "values": [ + "{Profile names: dev, test, staging, prod, etc.}", + "{Config file types: properties, yml, json}", + "{Activation method: spring.profiles.active, Maven, env vars}", + "{Number of profile-specific files}", + "{Profile-annotated components count}" + ] + }, + "execution_time_seconds": {elapsed_time}, + "timestamp": "{ISO 8601 timestamp}" +} +``` + +**Finding Examples**: +- ✅ Good: "Application uses comprehensive Spring profile system with 4 environments (dev, test, staging, prod) managed through application-{profile}.yml files and 15 @Profile-annotated configuration classes" +- ✅ Good: ".NET Core application with environment-based configuration using appsettings.{Environment}.json for Development, Staging, and Production environments activated via ASPNETCORE_ENVIRONMENT" +- ✅ Good: "Maven multi-profile build with 3 profiles (development, testing, production) controlling database connections, logging levels, and feature toggles" +- ✅ Good: "No environment profiles detected - single configuration approach suitable for utility application" +- ❌ Bad: "Profiles exist" +- ❌ Bad: "Environment configuration found" + +**Evidence Examples**: +- ✅ Good: "application-dev.yml, application-test.yml, application-staging.yml, application-prod.yml in src/main/resources/" +- ✅ Good: "15 configuration classes with @Profile annotations: @Profile('dev') in DevDatabaseConfig.java, @Profile('prod') in ProdSecurityConfig.java" +- ✅ Good: "Maven pom.xml defines 3 profiles with <id>development</id>, <id>testing</id>, <id>production</id> at lines 120-185" +- ✅ Good: "spring.profiles.active=dev in application.properties, overridable via SPRING_PROFILES_ACTIVE environment variable" +- ❌ Bad: "Configuration files found" +- ❌ Bad: "Profiles detected in code" + +## Error Handling + +### 1. No Profiles Found +- Verify it's not a single-environment application by design +- Check for alternative configuration mechanisms (external config, env vars only) +- Report finding: "No profile-based configuration detected - single configuration model" +- Set confidence to high if thorough search confirms absence + +### 2. Inconsistent Profile Names +- Report all variations found (dev vs development, prod vs production) +- Note potential misconfiguration risks +- Set confidence to medium +- Recommend standardization in evidence notes + +### 3. Partial Profile Implementation +- List which profiles are complete vs incomplete +- Note missing files (e.g., dev and prod exist but no test) +- Set confidence to medium with caveats + +### 4. Mixed Configuration Approaches +- Document each approach found (Spring profiles, Maven profiles, Docker configs) +- Clarify how they interact or if they're independent +- This is common and valid - report comprehensively + +### 5. Tool Failures +- If Glob returns too many results, refine patterns +- If Read fails on large files, use Grep for specific patterns +- After 3 retries, report partial results with notes + +## Example Complete Analysis + +**Scenario**: Spring Boot microservice with comprehensive profile setup + +**Steps Executed**: +1. Glob for Spring configs: Found application-{dev,test,staging,prod}.yml (4 files) +2. Read application.yml: Found spring.profiles.active: dev +3. Grep for @Profile: Found 18 matches in 15 configuration classes +4. Read pom.xml: No Maven profiles (uses Spring profiles only) +5. Checked Docker: Found docker-compose.dev.yml and docker-compose.prod.yml +6. Glob for K8s: Found k8s/dev/ and k8s/prod/ directories with manifests + +**Result**: +```json +{ + "input_name": "Profile Settings", + "analysis_method": "LLM", + "status": "success", + "result": { + "finding": "Application implements comprehensive 4-environment profile system (dev, test, staging, prod) using Spring profiles with separate YAML configs, 15 profile-annotated configuration classes, and environment-specific Docker/Kubernetes manifests", + "confidence": "high", + "evidence": [ + "4 Spring profile configs: application-dev.yml, application-test.yml, application-staging.yml, application-prod.yml in src/main/resources/", + "application.yml sets spring.profiles.active: dev as default, overridable via SPRING_PROFILES_ACTIVE env var", + "15 configuration classes use @Profile annotations: DevDatabaseConfig, ProdSecurityConfig, TestEmailConfig, etc.", + "docker-compose.dev.yml and docker-compose.prod.yml set different SPRING_PROFILES_ACTIVE values", + "Kubernetes manifests in k8s/dev/ and k8s/prod/ directories with environment-specific ConfigMaps", + "Profile-specific settings: database URLs, Redis hosts, feature flags, logging levels" + ], + "values": [ + "4 environments: dev, test, staging, prod", + "Spring profile activation via application.yml and environment variables", + "15 @Profile-annotated configuration classes", + "4 profile-specific YAML files (150-200 lines each)", + "Docker Compose configs for dev and prod", + "Kubernetes ConfigMaps per environment" + ] + }, + "execution_time_seconds": 35.2, + "timestamp": "2026-02-28T10:26:14Z" +} +``` diff --git a/.github/skills/fact-resource-limits/SKILL.md b/.github/skills/fact-resource-limits/SKILL.md new file mode 100644 index 000000000..ec0d4b3b8 --- /dev/null +++ b/.github/skills/fact-resource-limits/SKILL.md @@ -0,0 +1,103 @@ +--- +name: fact-resource-limits +description: Identify CPU/Memory resource limits for containers +--- + +# Resource Limits Analysis + +## Purpose +Identify CPU and memory resource constraints configured for containers through compose files or Kubernetes manifests. + +## Target Files/Locations +- **/docker-compose*.yml (deploy.resources) +- **/k8s/**/*.yaml (resources.requests, resources.limits) +- **/Dockerfile (no resource limits, but checked for context) + +## Example Patterns +- `memory: 2g`, `cpus: '1.5'` +- `limits: memory: "2Gi", cpu: "1000m"` +- `requests: memory: "512Mi", cpu: "250m"` + +## Analysis Steps + +### 1. Check docker-compose Resources +``` +Use Read: **/docker-compose*.yml +Look for deploy.resources section: + deploy: + resources: + limits: + cpus: '2.0' + memory: 2G + reservations: + cpus: '0.5' + memory: 512M +``` + +### 2. Analyze Kubernetes Resources +``` +Use Grep: "resources:|limits:|requests:|memory:|cpu:" +Files: **/k8s/**/*.yaml +Context: -B 3 -A 3 + +Parse format: + resources: + limits: + memory: "2Gi" + cpu: "1000m" + requests: + memory: "512Mi" + cpu: "250m" +``` + +### 3. Check for Resource Quotas +``` +Use Grep: "kind: ResourceQuota" +Files: **/k8s/**/*.yaml +Namespace-level resource constraints +``` + +## Confidence Determination + +### High Confidence +- ✅ Resource limits explicitly configured +- ✅ Both limits and requests defined +- **Example**: "Container limits: 2Gi memory, 1000m CPU; requests: 512Mi memory, 250m CPU" + +### Medium Confidence +- ⚠️ Only limits or only requests defined +- **Example**: "Memory limit 2GB configured, CPU unspecified" + +### Low Confidence +- ⚠️ No explicit resource configuration +- **Example**: "No resource limits configured, uses node defaults" + +### Not Applicable +- ❌ No container orchestration +- **Example**: "Direct Docker run, no resource management" + +## Output Format + +```json +{ + "input_name": "Resource Limits", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Resources summary}", + "confidence": "high|medium|low", + "evidence": [ + "{docker-compose deploy.resources}", + "{K8s limits/requests}", + "{ResourceQuota if present}" + ], + "values": [ + "{Memory limits and requests}", + "{CPU limits and requests}", + "{Units: Gi, Mi, millicores}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-runtime-environment/SKILL.md b/.github/skills/fact-runtime-environment/SKILL.md new file mode 100644 index 000000000..70d33be50 --- /dev/null +++ b/.github/skills/fact-runtime-environment/SKILL.md @@ -0,0 +1,107 @@ +--- +name: fact-runtime-environment +description: Identify application runtime (Node.js, Python, Java, Go, .NET) in container +--- + +# Runtime Environment Analysis + +## Purpose +Detect the application runtime/platform used in containerized applications (Node.js, Python, Java, Go, .NET, Ruby, PHP, etc.). + +## Target Files/Locations +- **/Dockerfile, **/Containerfile (FROM base image) +- **/package.json, **/package-lock.json (Node.js) +- **/pom.xml, **/build.gradle (Java) +- **/*.csproj, **/*.sln (.NET) +- **/requirements.txt, **/Pipfile, **/pyproject.toml (Python) +- **/go.mod (Go) +- **/Gemfile (Ruby) +- **/composer.json (PHP) + +## Example Patterns +- `FROM node:16`, `FROM python:3.9`, `FROM openjdk:11`, `FROM mcr.microsoft.com/dotnet/runtime:6.0` +- `RUN npm install`, `RUN pip install`, `RUN go build`, `RUN dotnet publish` + +## Analysis Steps + +### 1. Analyze Base Image +``` +Use Grep: "FROM\\s+(node|python|openjdk|golang|mcr.microsoft.com/dotnet|ruby|php)" +Files: **/Dockerfile +Extract runtime from base image name +``` + +### 2. Check Build/Install Commands +``` +Use Grep: "npm|pip|maven|gradle|dotnet|go build|bundle|composer" +Files: **/Dockerfile +Context: -B 1 -A 2 +``` + +### 3. Identify Dependency Files +``` +Use Glob to find: +- package.json, package-lock.json (Node.js) +- requirements.txt, Pipfile (Python) +- pom.xml, build.gradle (Java) +- *.csproj, *.sln (.NET) +- go.mod, go.sum (Go) +- Gemfile (Ruby) +- composer.json (PHP) +``` + +### 4. Check Version from Base Image +``` +Parse FROM instruction: +- node:16-alpine → Node.js 16 +- python:3.9-slim → Python 3.9 +- openjdk:11-jre → Java 11 +- mcr.microsoft.com/dotnet/runtime:6.0 → .NET 6 +``` + +## Confidence Determination + +### High Confidence +- ✅ Runtime base image clearly specified +- ✅ Build commands match runtime +- ✅ Dependency files present +- **Example**: "Node.js 16 runtime based on FROM node:16-alpine and package.json" + +### Medium Confidence +- ⚠️ Generic base with runtime installed +- ⚠️ Multi-language project +- **Example**: "Java application, version unclear (uses maven but base is Ubuntu)" + +### Low Confidence +- ⚠️ No clear runtime indicators +- **Example**: "Compiled binary, runtime unclear" + +### Not Applicable +- ❌ No container +- **Example**: "No Dockerfile found" + +## Output Format + +```json +{ + "input_name": "Runtime Environment", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Runtime identified}", + "confidence": "high|medium|low", + "evidence": [ + "{Base image}", + "{Build commands}", + "{Dependency files}" + ], + "values": [ + "{Runtime: Node.js, Python, Java, .NET, Go, etc.}", + "{Version}", + "{Variant: alpine, slim, etc.}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-security-implementation/SKILL.md b/.github/skills/fact-security-implementation/SKILL.md new file mode 100644 index 000000000..7f118bd52 --- /dev/null +++ b/.github/skills/fact-security-implementation/SKILL.md @@ -0,0 +1,142 @@ +--- +name: fact-security-implementation +description: Analyze security measures (HTTPS, Encryption, Token-based auth) +--- + +# Security Implementation Analysis + +## Purpose +Identify security measures implemented in the application including HTTPS, authentication, authorization, encryption, and security headers. + +## Target Files/Locations +- **/application.{properties,yml} (SSL, security configs) +- **/*.java, **/*.cs (security filters, auth) +- **/pom.xml, **/build.gradle, **/*.csproj (security dependencies) +- **/Dockerfile (SSL certificates) +- **/k8s/**/*.yaml (TLS secrets) + +## Example Patterns +- **HTTPS/TLS**: server.ssl.*, SSLContext, TLS certificates +- **Authentication**: JWT, OAuth2, Basic Auth, API keys +- **Authorization**: @PreAuthorize, [Authorize], RBAC +- **Encryption**: AES, RSA, EncryptionService +- **Security Headers**: CORS, CSP, HSTS, X-Frame-Options + +## Analysis Steps + +### 1. Check for HTTPS/TLS Configuration +``` +Use Grep: "server\\.ssl|https://|TLS|SSLContext|keystore" +Files: **/application.{properties,yml}, **/*.java, **/*.cs +Context: -B 2 -A 2 + +Check for: +- server.ssl.key-store +- SSLContext.getInstance("TLS") +- HTTPS redirect configurations +``` + +### 2. Check Authentication Mechanisms +``` +Use Grep: "JWT|OAuth2|@EnableWebSecurity|JwtToken|Bearer|Basic Auth" +Files: **/*.{java,cs,js} +Context: -B 3 -A 3 + +Dependencies: +- spring-boot-starter-security +- Microsoft.AspNetCore.Authentication.JwtBearer +- passport (Node.js) + +Look for: +- @EnableWebSecurity, @PreAuthorize (Spring) +- [Authorize], UseAuthentication() (.NET) +- JWT token generation/validation +``` + +### 3. Check for Authorization +``` +Use Grep: "@PreAuthorize|@Secured|\\[Authorize\\]|hasRole|hasAuthority" +Files: **/*.{java,cs} + +RBAC indicators: +- Role definitions +- Permission checks +- Access control lists +``` + +### 4. Check for Encryption +``` +Use Grep: "AES|RSA|encrypt|decrypt|Cipher|CryptoService" +Files: **/*.{java,cs,py,js} + +Look for: +- Data encryption at rest +- Encryption services +- Key management (KMS, KeyVault) +``` + +### 5. Check for Security Headers +``` +Use Grep: "CORS|Content-Security-Policy|X-Frame-Options|HSTS|Strict-Transport-Security" +Files: **/*.{java,cs,js}, **/application.{properties,yml} + +Spring: WebMvcConfigurer.addCorsMappings +.NET: app.UseCors(), app.UseHsts() +``` + +### 6. Check Dependency Scanning +``` +Look for security scanning: +- Dependabot config +- Snyk, OWASP Dependency Check +- npm audit, dotnet list package --vulnerable +``` + +## Confidence Determination + +### High Confidence +- ✅ Multiple security measures implemented +- ✅ HTTPS + authentication + authorization configured +- **Example**: "HTTPS with TLS 1.3, JWT authentication, role-based authorization, AES encryption for sensitive data" + +### Medium Confidence +- ⚠️ Some security features but incomplete +- **Example**: "Basic authentication configured, HTTPS unclear" + +### Low Confidence +- ⚠️ Security dependencies present but implementation unclear +- **Example**: "Security framework dependency but no explicit configuration found" + +### Not Applicable +- ❌ Internal tool with no security requirements +- **Example**: "Development utility, no security implementation needed" + +## Output Format + +```json +{ + "input_name": "Security Implementation", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Security summary}", + "confidence": "high|medium|low", + "evidence": [ + "{HTTPS/TLS configuration}", + "{Authentication mechanism}", + "{Authorization approach}", + "{Encryption usage}", + "{Security headers}" + ], + "values": [ + "{Transport: HTTPS, TLS 1.2/1.3}", + "{Auth: JWT, OAuth2, Basic}", + "{Authorization: RBAC, attribute-based}", + "{Encryption: AES-256, RSA}", + "{Headers: CORS, CSP, HSTS}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-service-definition/SKILL.md b/.github/skills/fact-service-definition/SKILL.md new file mode 100644 index 000000000..ac3287c5c --- /dev/null +++ b/.github/skills/fact-service-definition/SKILL.md @@ -0,0 +1,108 @@ +--- +name: fact-service-definition +description: Analyze service definition files (docker-compose.yml, K8s manifests) +--- + +# Service Definition Analysis + +## Purpose +Catalog and analyze service definition files that describe how the application is deployed and orchestrated. + +## Target Files/Locations +- **/docker-compose*.yml, **/docker-compose.*.yml +- **/k8s/**/*.yaml, **/manifests/**/*.yaml +- **/helm/**/templates/*.yaml + +## Example Patterns +- Docker Compose services with image, ports, volumes, environment +- Kubernetes Deployments, Services, ConfigMaps, Secrets +- Helm charts with templated manifests + +## Analysis Steps + +### 1. Catalog Docker Compose Files +``` +Use Glob: **/docker-compose*.yml +For each file: +- Count services defined +- Check for extends or depends_on +- Note environment variants (dev, prod) +``` + +### 2. Catalog Kubernetes Manifests +``` +Use Glob: **/k8s/**/*.yaml, **/manifests/**/*.yaml +Use Grep: "kind: Deployment|kind: Service|kind: ConfigMap|kind: Secret" +Files: **/*.yaml +Count by kind + +Group by resource type: +- Workloads: Deployment, StatefulSet, DaemonSet, Job +- Services: Service, Ingress +- Config: ConfigMap, Secret +- Storage: PersistentVolumeClaim +``` + +### 3. Check for Helm Charts +``` +Use Glob: **/Chart.yaml, **/values.yaml +If found: Helm is used +Count template files +``` + +### 4. Analyze Service Complexity +``` +For Compose: +- Single service vs multi-service +- Service dependencies (depends_on) + +For K8s: +- Microservices count +- Service mesh indicators +- Namespace organization +``` + +## Confidence Determination + +### High Confidence +- ✅ Service files present and parseable +- ✅ Clear service structure +- **Example**: "3-service docker-compose with web, api, database services fully configured" + +### Medium Confidence +- ⚠️ Some service files found but structure unclear +- **Example**: "K8s manifests present but relationships between services unclear" + +### Low Confidence +- ⚠️ Service definitions incomplete +- **Example**: "Partial service definitions, missing critical resources" + +### Not Applicable +- ❌ No service definitions +- **Example**: "Single container, no orchestration files" + +## Output Format + +```json +{ + "input_name": "Service Definition", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Service definitions summary}", + "confidence": "high|medium|low", + "evidence": [ + "{File types and counts}", + "{Service/resource counts}", + "{Orchestration approach}" + ], + "values": [ + "{Tool: Compose, K8s, Helm}", + "{Service count}", + "{Resource types and counts}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-servlet-container/SKILL.md b/.github/skills/fact-servlet-container/SKILL.md new file mode 100644 index 000000000..cf206f209 --- /dev/null +++ b/.github/skills/fact-servlet-container/SKILL.md @@ -0,0 +1,310 @@ +--- +name: fact-servlet-container +description: Identify servlet container requirements and version +--- + +# Servlet Container Analysis + +## Purpose +Identify the servlet container (application server) requirements for the application, including Servlet API version, container-specific features, and deployment model. This helps determine migration compatibility and modernization path. + +## Target Files/Locations +- **Deployment descriptors**: **/WEB-INF/web.xml, **/META-INF/weblogic.xml, **/META-INF/jboss-web.xml +- **Build files**: **/pom.xml, **/build.gradle, **/build.gradle.kts +- **Configuration**: **/application.properties, **/application.yml +- **Server configs**: **/server.xml, **/context.xml (Tomcat), **/standalone.xml (JBoss/WildFly) +- **Java source**: **/*Servlet.java, **/*Filter.java, **/*Listener.java + +## Example Patterns to Search +- **Servlet API**: `javax.servlet`, `jakarta.servlet`, `ServletContext`, `HttpServlet` +- **Servlet version**: `<web-app version="3.1"`, `version="4.0"`, `version="5.0"` +- **Container-specific**: `weblogic`, `jboss`, `wildfly`, `tomcat`, `websphere`, `glassfish` +- **Server dependencies**: `provided` scope for servlet-api, tomcat-embed, wildfly-swarm +- **Annotations**: `@WebServlet`, `@WebFilter`, `@WebListener`, `@MultipartConfig` + +## Analysis Steps + +### 1. Analyze web.xml Deployment Descriptor +``` +Use Glob to find web.xml: +- **/WEB-INF/web.xml +- **/META-INF/web.xml + +If found, use Read to examine: +- <web-app> version attribute (2.3, 2.4, 2.5, 3.0, 3.1, 4.0, 5.0, 6.0) +- XSD/DTD namespace for Servlet version + - Java EE 5 (Servlet 2.5): java.sun.com/xml/ns/javaee + - Java EE 6-7 (Servlet 3.0-3.1): xmlns.jcp.org/xml/ns/javaee + - Jakarta EE 8+ (Servlet 4.0+): jakarta.ee/xml/ns/jakartaee +- Servlet definitions and mappings +- Filter configurations +- Container-specific elements +``` + +### 2. Check Build Dependencies for Servlet API +``` +Use Glob to find build files: +- **/pom.xml +- **/build.gradle +- **/build.gradle.kts + +Use Read or Grep to search for: +Maven (pom.xml): +- <artifactId>servlet-api</artifactId> +- <artifactId>javax.servlet-api</artifactId> +- <artifactId>jakarta.servlet-api</artifactId> +- <version>3.1.0</version>, <version>4.0.0</version>, <version>5.0.0</version> +- <scope>provided</scope> (indicates external container) + +Gradle (build.gradle): +- providedCompile 'javax.servlet:javax.servlet-api:3.1.0' +- compileOnly 'jakarta.servlet:jakarta.servlet-api:5.0.0' + +Check for embedded container dependencies: +- spring-boot-starter-web (embedded Tomcat) +- tomcat-embed-core +- jetty-server +- undertow-core +``` + +### 3. Search for Container-Specific Configuration +``` +Use Glob to find container configs: +- **/META-INF/weblogic.xml (Oracle WebLogic) +- **/META-INF/jboss-web.xml (JBoss/WildFly) +- **/META-INF/glassfish-web.xml (GlassFish) +- **/META-INF/geronimo-web.xml (Apache Geronimo) +- **/WEB-INF/ibm-web-ext.xml (IBM WebSphere) + +If found, this indicates container-specific features/requirements +``` + +### 4. Search for Servlet/Filter Implementations +``` +Use Grep to find servlet code: +Pattern: "extends\\s+HttpServlet|implements\\s+Servlet|implements\\s+Filter" +Files: **/*.java +Context: -B 2 -A 5 + +Use Grep to find servlet annotations: +Pattern: "@WebServlet|@WebFilter|@WebListener|@MultipartConfig" +Files: **/*.java +Context: -B 1 -A 3 + +Analyze: +- Count of servlets/filters +- Use of Servlet 3.0+ annotations vs web.xml +- Async servlet support (@WebServlet(asyncSupported=true)) +``` + +### 5. Check for Spring Boot Embedded Container +``` +Use Grep in pom.xml or build.gradle: +Pattern: "spring-boot-starter-web|spring-boot-starter-tomcat|spring-boot-starter-jetty|spring-boot-starter-undertow" + +If found: +- This is embedded container (not external) +- Check application.properties for server configuration + - server.port + - server.servlet.context-path + - server.tomcat.* (if Tomcat) + +Use Glob for Spring Boot config: +- **/application.properties +- **/application.yml +``` + +### 6. Identify Container Version from Dependencies +``` +Parse Maven/Gradle files for exact versions: +- javax.servlet-api: 2.5, 3.0, 3.1 (Java EE) +- jakarta.servlet-api: 4.0 (Jakarta EE 8), 5.0 (Jakarta EE 9), 6.0 (Jakarta EE 10) + +Match to Servlet API specifications: +- Servlet 2.5 = Java EE 5 (Tomcat 6, JBoss 5) +- Servlet 3.0 = Java EE 6 (Tomcat 7, JBoss 7, GlassFish 3) +- Servlet 3.1 = Java EE 7 (Tomcat 8, WildFly 8-10, WebLogic 12c) +- Servlet 4.0 = Java EE 8 / Jakarta EE 8 (Tomcat 9, WildFly 14+) +- Servlet 5.0 = Jakarta EE 9 (Tomcat 10, WildFly 22+) +- Servlet 6.0 = Jakarta EE 10 (Tomcat 10.1+, WildFly 27+) +``` + +### 7. Check for Container-Specific Features +``` +Use Grep to search for container-specific APIs: +- WebLogic: "weblogic\\..*|WorkManager|JMS" +- JBoss/WildFly: "org\\.jboss|org\\.wildfly|EJB" +- WebSphere: "com\\.ibm\\.websphere" +- Tomcat: "org\\.apache\\.catalina|org\\.apache\\.tomcat" + +These indicate tight coupling to specific containers +``` + +## Confidence Determination + +### High Confidence Criteria +Clear and definitive evidence of servlet container requirements: +- ✅ web.xml present with explicit version attribute +- ✅ Servlet API dependency with specific version in build file +- ✅ Container-specific configuration files found +- ✅ Servlet/Filter implementations found in code +- ✅ Clear deployment model (embedded vs external container) + +**Examples**: +- "Web application requires Servlet 3.1 API (Java EE 7) based on web.xml version='3.1' and javax.servlet-api:3.1.0 dependency" +- "Spring Boot application with embedded Tomcat 9.0.65 (Servlet 4.0) from spring-boot-starter-web:2.7.3" +- "WebLogic-specific deployment with weblogic.xml and WorkManager configuration - requires Oracle WebLogic 12c+" + +### Medium Confidence Criteria +Partial evidence or inferred information: +- ⚠️ Servlet API dependency present but no web.xml (annotation-based config) +- ⚠️ Container type inferred from Spring Boot starter but version unclear +- ⚠️ Servlet code found but no explicit version indicators +- ⚠️ Legacy web.xml without version attribute +- ⚠️ Mixed signals (multiple container dependencies) + +**Examples**: +- "Servlet 3.0+ usage inferred from @WebServlet annotations, but no explicit version in dependencies" +- "Spring Boot with default embedded container (likely Tomcat) but version not specified" +- "Servlet implementations found but build file doesn't declare servlet-api dependency explicitly" + +### Low Confidence Criteria +Weak or ambiguous evidence: +- ⚠️ No web.xml or servlet annotations found +- ⚠️ Servlet API in transitive dependencies only +- ⚠️ Container type unclear or multiple possibilities +- ⚠️ Test code has servlet dependencies but main code doesn't +- ⚠️ Comments reference servlets but no actual implementation + +**Examples**: +- "Servlet API appears in dependency tree but no servlet code found" +- "No clear servlet container indicators - may be non-web application" +- "Test dependencies include servlet-api but unclear if production code uses it" + +### Not Applicable Criteria +When servlet container analysis doesn't apply: +- ❌ Non-web application (standalone, batch, CLI tool) +- ❌ REST API using JAX-RS without servlets (Jersey, RESTEasy standalone) +- ❌ Pure reactive application (Spring WebFlux on Netty) +- ❌ Different platform (.NET, Node.js, Python) +- ❌ Library/framework project (no executable component) + +**Examples**: +- "Spring Boot application using WebFlux and Netty - no servlet container required" +- "Standalone Java application with no web components" +- "Node.js Express application - servlet analysis not applicable" + +## Output Format + +**CRITICAL**: Use the `write_assessment_result` tool (not just output JSON text). + +```json +{ + "input_name": "Servlet Container", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Clear 1-2 sentence summary of servlet container requirements}", + "confidence": "high|medium|low", + "evidence": [ + "{web.xml version or absence}", + "{Servlet API dependency with version}", + "{Container-specific files or features}", + "{Deployment model (embedded/external)}", + "{Container type identified}" + ], + "values": [ + "{Servlet API version (e.g., 3.1, 4.0, 5.0)}", + "{Java EE / Jakarta EE version}", + "{Container type and version (if known)}", + "{Number of servlets/filters}", + "{Container-specific features used}" + ] + }, + "execution_time_seconds": {elapsed_time}, + "timestamp": "{ISO 8601 timestamp}" +} +``` + +**Finding Examples**: +- ✅ Good: "Application requires Servlet 3.1 container (Java EE 7) deployed to external WebLogic 12c server with WebLogic-specific WorkManager configuration" +- ✅ Good: "Spring Boot application with embedded Tomcat 9.0.65 (Servlet 4.0 / Jakarta EE 8) managed by spring-boot-starter-web" +- ✅ Good: "Modern Jakarta EE 9 application requiring Servlet 5.0 container (WildFly 22+, Tomcat 10+) with 8 servlets and 5 filters" +- ✅ Good: "Non-web application - no servlet container required (standalone Spring Boot with WebFlux)" +- ❌ Bad: "Uses servlets" +- ❌ Bad: "Container required" + +**Evidence Examples**: +- ✅ Good: "web.xml at WEB-INF/web.xml declares version='3.1' with Java EE 7 namespace" +- ✅ Good: "javax.servlet-api:3.1.0 with <scope>provided</scope> in pom.xml" +- ✅ Good: "weblogic.xml found with WorkManager 'default' configuration" +- ✅ Good: "spring-boot-starter-web:2.7.3 includes tomcat-embed-core:9.0.65" +- ✅ Good: "5 servlets using @WebServlet annotations (Servlet 3.0+ feature)" +- ❌ Bad: "web.xml exists" +- ❌ Bad: "Container dependency found" + +## Error Handling + +### 1. No Servlet Evidence Found +- Check if this is a web application at all +- Search for alternative web frameworks (JAX-RS, Spring WebFlux) +- Report as "not_applicable" if truly not a web app +- If web app with no servlet evidence: low confidence "Unable to determine container requirements" + +### 2. Mixed Servlet API Versions +- Report all versions found with locations +- Set confidence to medium +- Note potential migration status: "Project in transition from javax.servlet to jakarta.servlet" + +### 3. Embedded vs External Container Confusion +- Check for spring-boot packaging (jar vs war) +- Spring Boot jar = embedded, war = external +- Report both possibilities if unclear + +### 4. Tool Failures +- If Grep fails on large pom.xml, use Read with offset/limit +- If XML parsing issues, try grep for version patterns +- After 3 retries, report with caveats + +## Example Complete Analysis + +**Scenario**: Traditional Java EE web application on WebLogic + +**Steps Executed**: +1. Glob for web.xml: Found WEB-INF/web.xml +2. Read web.xml: version="3.1", Java EE 7 namespace +3. Read pom.xml: javax.servlet-api:3.1.0 with provided scope +4. Glob for container configs: Found META-INF/weblogic.xml +5. Grep for servlets: Found 8 servlet classes, 5 filters +6. Grep for container features: Found WorkManager references + +**Result**: +```json +{ + "input_name": "Servlet Container", + "analysis_method": "LLM", + "status": "success", + "result": { + "finding": "Application requires Servlet 3.1 (Java EE 7) container, specifically Oracle WebLogic 12c, with WebLogic-specific features including WorkManager and JMS integration", + "confidence": "high", + "evidence": [ + "web.xml at WEB-INF/web.xml with version='3.1' and Java EE 7 namespace (xmlns.jcp.org/xml/ns/javaee)", + "javax.servlet-api:3.1.0 dependency with <scope>provided</scope> in pom.xml", + "weblogic.xml at META-INF/weblogic.xml with WorkManager configuration", + "8 servlet implementations: AuthServlet, MainServlet, UploadServlet, etc.", + "5 filter implementations including CharacterEncodingFilter and AuthenticationFilter", + "WebLogic-specific API usage: weblogic.jms.* and weblogic.servlet.*" + ], + "values": [ + "Servlet API 3.1", + "Java EE 7", + "Oracle WebLogic 12c (minimum)", + "8 servlets, 5 filters", + "WebLogic WorkManager 'default-workmanager'", + "WAR packaging for external deployment" + ] + }, + "execution_time_seconds": 28.4, + "timestamp": "2026-02-28T10:22:38Z" +} +``` diff --git a/.github/skills/fact-startup-instrumentation/SKILL.md b/.github/skills/fact-startup-instrumentation/SKILL.md new file mode 100644 index 000000000..044800ba3 --- /dev/null +++ b/.github/skills/fact-startup-instrumentation/SKILL.md @@ -0,0 +1,371 @@ +--- +name: fact-startup-instrumentation +description: Analyze startup instrumentation (logging, telemetry, AOP) +--- + +# Startup Instrumentation Analysis + +## Purpose +Detect and analyze logging frameworks, telemetry/APM tools, and aspect-oriented programming (AOP) components initialized at application startup. + +## Analysis Strategy + +This SKILL searches for configuration files, startup code, and dependency declarations to identify instrumentation frameworks. + +## Analysis Steps + +### 1. Logging Framework Detection + +**Search for Logging Configuration Files:** +```bash +# Use Glob to find config files +Glob patterns: +- **/logback.xml, **/logback-spring.xml +- **/log4j2.xml, **/log4j2.yml, **/log4j.properties +- **/appsettings*.json (for Serilog/NLog in .NET) +- **/logging.conf, **/logging.yaml (Python) +- **/winston.config.js (Node.js) +``` + +**Search for Logging Dependencies:** +```bash +# Use Grep to find logging libraries +Pattern: "logback|log4j2|slf4j|serilog|nlog|ilogger|winston|bunyan|pino|logging\.getLogger" +Files: **/pom.xml, **/build.gradle, **/*.csproj, **/package.json, **/requirements.txt, **/Gemfile +``` + +**Search for Logger Initialization in Code:** +```bash +Pattern: "LoggerFactory|ILogger|getLogger|Logger\.getLogger|createLogger|logging\.basicConfig" +Files: **/Program.cs, **/Startup.cs, **/Main.java, **/*Application.java, **/app.py, **/main.py, **/server.js, **/app.js +``` + +### 2. Telemetry & APM Detection + +**Application Insights (.NET/Java):** +```bash +Pattern: "applicationinsights|Microsoft\.ApplicationInsights|TelemetryClient" +Files: **/*.csproj, **/pom.xml, **/appsettings.json, **/ApplicationInsights.config +``` + +**OpenTelemetry (Cross-platform):** +```bash +Pattern: "opentelemetry|otel|TracerProvider|MeterProvider" +Files: **/pom.xml, **/build.gradle, **/*.csproj, **/package.json, **/requirements.txt +``` + +**New Relic:** +```bash +Pattern: "newrelic|New Relic" +Files: **/newrelic.yml, **/newrelic.config, **/newrelic.js +``` + +**Datadog:** +```bash +Pattern: "datadog|dd-trace|ddtrace" +Files: **/pom.xml, **/package.json, **/requirements.txt, **/Gemfile +``` + +**Dynatrace:** +```bash +Pattern: "dynatrace|oneagent" +Files: **/*.config, **/dockerfile, **/deployment.yaml +``` + +**Elastic APM:** +```bash +Pattern: "elastic-apm|ElasticApm" +Files: **/pom.xml, **/*.csproj, **/package.json, **/requirements.txt +``` + +### 3. Aspect-Oriented Programming (AOP) Detection + +**Spring AOP (Java):** +```bash +Pattern: "@Aspect|@Before|@After|@Around|spring-aop|aspectjweaver" +Files: **/*.java, **/pom.xml, **/build.gradle +``` + +**PostSharp (.NET):** +```bash +Pattern: "PostSharp|[MethodInterception]|[OnMethodBoundary]" +Files: **/*.cs, **/*.csproj +``` + +**AspectJ:** +```bash +Pattern: "aspectj|@Pointcut|@Aspect" +Files: **/*.java, **/aop.xml, **/pom.xml +``` + +### 4. Startup Code Analysis + +**Check Main Entry Points:** +```bash +# Use Glob to find entry points +Patterns: +- **/Program.cs (ASP.NET Core) +- **/Startup.cs (ASP.NET Core) +- **/Main.java, **/*Application.java (Spring Boot) +- **/app.py, **/main.py, **/__init__.py (Python) +- **/server.js, **/app.js, **/index.js (Node.js) +``` + +**Analyze Startup Configuration:** +```bash +# Read entry point files and check for: +- Logger configuration: builder.Logging.Add*, LogManager.Setup() +- Telemetry setup: services.AddApplicationInsightsTelemetry() +- AOP configuration: services.EnableAspectOrientedProgramming() +``` + +## Framework-Specific Patterns + +### Java/Spring Boot + +**Logback:** +```xml +<!-- logback-spring.xml --> +<configuration> + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + ... +``` +```java +// Code pattern +private static final Logger logger = LoggerFactory.getLogger(Application.class); +``` + +**Log4j2:** +```xml +<!-- log4j2.xml --> +<Configuration status="WARN"> + <Appenders> +``` +```java +private static final Logger logger = LogManager.getLogger(Application.class); +``` + +### .NET/ASP.NET Core + +**Serilog:** +```csharp +// Program.cs +Log.Logger = new LoggerConfiguration() + .WriteTo.Console() + .WriteTo.File("logs/log.txt") + .CreateLogger(); +``` +```json +// appsettings.json +"Serilog": { + "Using": ["Serilog.Sinks.Console", "Serilog.Sinks.File"] +} +``` + +**NLog:** +```csharp +// Program.cs +builder.Logging.ClearProviders(); +builder.Host.UseNLog(); +``` +```xml +<!-- nlog.config --> +<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"> +``` + +**ILogger (Built-in):** +```csharp +builder.Logging.AddConsole(); +builder.Logging.AddDebug(); +builder.Logging.AddApplicationInsights(); +``` + +### Python + +**Logging Module:** +```python +import logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) +``` + +**Loguru:** +```python +from loguru import logger +logger.add("file.log", rotation="500 MB") +``` + +### Node.js + +**Winston:** +```javascript +const winston = require('winston'); +const logger = winston.createLogger({ + transports: [ + new winston.transports.Console(), + new winston.transports.File({ filename: 'combined.log' }) + ] +}); +``` + +**Pino:** +```javascript +const pino = require('pino'); +const logger = pino(); +``` + +## Analysis Decision Logic + +1. **Check for configuration files** → High confidence if config exists +2. **Check for dependencies** → Medium confidence +3. **Check for code usage** → Confirms framework is actively used +4. **Check for multiple frameworks** → List all detected + +## Confidence Levels + +- **High**: Configuration file + dependency + code usage detected +- **Medium**: Dependency + code usage (no explicit config) +- **Low**: Only dependency found (may not be actively used) + +## Output Format + +After analysis, call the MCP tool: + +```javascript +write_assessment_result({ + resultJson: JSON.stringify({ + input_name: "Startup Instrumentation", + analysis_method: "Hybrid", // Code + LLM analysis + status: "success", // or "not_applicable" or "failed" + result: { + finding: "Serilog with Application Insights and custom AOP interceptors", + confidence: "high", // high, medium, or low + evidence: [ + "Found appsettings.json with Serilog configuration (Console, File, Application Insights sinks)", + "Program.cs configures Serilog at line 12: Log.Logger = new LoggerConfiguration()...", + "ApplicationInsights.config found with instrumentation key", + "Custom AOP: Found 3 MethodInterceptionAspect classes for logging, caching, and validation" + ], + values: [ + "Serilog 3.1.1", + "Application Insights", + "Custom AOP (MethodInterception)" + ], + instrumentation_details: { + logging_framework: "Serilog", + logging_version: "3.1.1", + log_sinks: ["Console", "File", "ApplicationInsights"], + telemetry_provider: "Application Insights", + aop_framework: "Custom (MethodInterception)", + startup_file: "Program.cs" + } + }, + execution_time_seconds: 2.8, + timestamp: new Date().toISOString() + }), + assessmentDir: variables.assessment_dir +}); +``` + +## Multiple Framework Examples + +### Example 1: Spring Boot with Logback + Elastic APM + Spring AOP +```json +{ + "finding": "Logback with Elastic APM and Spring AOP", + "confidence": "high", + "evidence": [ + "logback-spring.xml found with RollingFileAppender", + "pom.xml includes elastic-apm-agent 1.39.0", + "Found 8 @Aspect classes for cross-cutting concerns", + "Application.java initializes APM agent at startup" + ], + "values": ["Logback", "Elastic APM 1.39.0", "Spring AOP", "AspectJ"] +} +``` + +### Example 2: Python with Loguru + OpenTelemetry +```json +{ + "finding": "Loguru with OpenTelemetry", + "confidence": "medium", + "evidence": [ + "requirements.txt includes loguru==0.7.0", + "app.py configures loguru with rotation and retention", + "Found opentelemetry-api in requirements.txt", + "No explicit OpenTelemetry initialization code found" + ], + "values": ["Loguru 0.7.0", "OpenTelemetry (partial)"] +} +``` + +### Example 3: Node.js with Winston + Datadog +```json +{ + "finding": "Winston with Datadog APM", + "confidence": "high", + "evidence": [ + "winston.config.js configures Console, File, and HTTP transports", + "package.json includes winston@3.11.0 and dd-trace@4.23.0", + "server.js imports and initializes dd-trace at line 1", + "Found custom Winston format for JSON structured logging" + ], + "values": ["Winston 3.11.0", "Datadog APM 4.23.0"] +} +``` + +## Not Applicable Scenarios + +Return `status: "not_applicable"` if: +- No logging framework detected (using only print/console.log) +- Project is a library without startup entry point +- CLI tool with minimal logging requirements + +Example: +```json +{ + "input_name": "Startup Instrumentation", + "analysis_method": "Code", + "status": "not_applicable", + "result": { + "finding": "No structured logging framework detected", + "confidence": "high", + "evidence": [ + "No logging configuration files found", + "No logging dependencies in package.json", + "Only console.log statements found in code" + ], + "values": [] + }, + "execution_time_seconds": 1.2, + "timestamp": "2026-03-01T01:00:00Z" +} +``` + +## Error Handling + +Return `status: "failed"` if: +- Unable to access project files +- Critical analysis error occurred +- Timeout during file search + +Include error details: +```json +{ + "status": "failed", + "result": { + "finding": "Analysis failed", + "confidence": "low", + "evidence": ["Error: Permission denied reading configuration files"], + "values": [] + } +} +``` + +## Notes + +- Applications often use multiple logging/telemetry tools (e.g., Serilog + Application Insights) +- List all detected frameworks in `values` array +- Prioritize most prominent framework in `finding` +- AOP frameworks are often used for logging interception - include in analysis +- Check both code and configuration for complete picture diff --git a/.github/skills/fact-system-packages/SKILL.md b/.github/skills/fact-system-packages/SKILL.md new file mode 100644 index 000000000..c315120b6 --- /dev/null +++ b/.github/skills/fact-system-packages/SKILL.md @@ -0,0 +1,99 @@ +--- +name: fact-system-packages +description: Identify system packages installed in container (nginx, curl, git, etc.) +--- + +# System Packages Analysis + +## Purpose +Identify OS-level system packages installed in the container image through package manager commands. + +## Target Files/Locations +- **/Dockerfile, **/Containerfile + +## Example Patterns +- `RUN apt-get install nginx curl git` +- `RUN apk add --no-cache ca-certificates tzdata` +- `RUN yum install -y postgresql-client` + +## Analysis Steps + +### 1. Find Package Installation Commands +``` +Use Grep: "apt-get install|apt install|apk add|yum install|dnf install" +Files: **/Dockerfile +Context: -A 5 (capture multi-line installs) +``` + +### 2. Extract Package Names +``` +Parse package manager commands: +- apt: after "install" keyword +- apk: after "add" keyword +- yum/dnf: after "install" keyword + +Handle flags: --no-cache, -y, --no-install-recommends +Handle line continuations with \ +``` + +### 3. Categorize Packages +``` +Group by purpose: +- Web servers: nginx, apache2 +- SSL/Certificates: ca-certificates, openssl +- Development tools: git, curl, wget, vim +- Database clients: postgresql-client, mysql-client +- Build tools: gcc, make, build-essential +- Utilities: tzdata, bash, coreutils +``` + +### 4. Count Total Packages +``` +Sum all packages across all RUN commands +Note if cleanup commands present (apt-get clean, rm -rf /var/lib/apt/lists/*) +``` + +## Confidence Determination + +### High Confidence +- ✅ Package install commands clearly visible +- ✅ Package names explicitly listed +- **Example**: "15 system packages installed: nginx, curl, git, ca-certificates, tzdata, etc." + +### Medium Confidence +- ⚠️ Some packages via install scripts +- **Example**: "Packages installed via apt but list partially in script files" + +### Low Confidence +- ⚠️ Base image includes packages, additions unclear +- **Example**: "Base image may include packages, specific additions unclear" + +### Not Applicable +- ❌ No container or minimal base (scratch, distroless) +- **Example**: "Uses scratch base image with no package manager" + +## Output Format + +```json +{ + "input_name": "System Packages", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Packages summary}", + "confidence": "high|medium|low", + "evidence": [ + "{Package manager commands}", + "{Package count}", + "{Installation patterns}" + ], + "values": [ + "{Package names list}", + "{Categories: web, ssl, dev tools, etc.}", + "{Package manager: apt, apk, yum}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-testing-framework/SKILL.md b/.github/skills/fact-testing-framework/SKILL.md new file mode 100644 index 000000000..97d5e5b9d --- /dev/null +++ b/.github/skills/fact-testing-framework/SKILL.md @@ -0,0 +1,139 @@ +--- +name: fact-testing-framework +description: Analyze testing tools and frameworks used in the project +--- + +# Testing Framework Analysis + +## Purpose +Detect testing frameworks and tools used in the codebase. + +## Target Files/Locations +- **/pom.xml, **/build.gradle, **/build.gradle.kts (Java) +- **/*.csproj (C#/.NET) +- **/package.json (Node.js) +- **/requirements.txt, **/requirements-dev.txt, **/setup.py, **/pyproject.toml (Python) +- **/go.mod (Go) +- **/test/**/*.{java,cs,py,js,ts}, **/*Test.{java,cs}, **/*Tests.cs, **/*.test.{js,ts}, **/*.spec.{js,ts}, **/test_*.py, **/*_test.go + +## Analysis Steps + +### 1. Check Java Testing Frameworks (Maven/Gradle) +``` +Use Grep: "junit-jupiter|<artifactId>junit</artifactId>|testng|mockito" +Files: **/pom.xml, **/build.gradle, **/build.gradle.kts + +Map findings: +- junit-jupiter → JUnit 5 +- <artifactId>junit</artifactId> → JUnit 4 +- testng → TestNG +- mockito → Mockito +``` + +### 2. Check .NET Testing Frameworks +``` +Use Grep: "xunit|nunit|MSTest" +Files: **/*.csproj + +Map findings: +- xunit → xUnit +- nunit → NUnit +- MSTest → MSTest +``` + +### 3. Check Node.js Testing Frameworks +``` +Use Grep: "\"jest\"|\"@types/jest\"|\"mocha\"|\"@types/mocha\"|\"chai\"|\"jasmine\"|\"vitest\"|\"@vitest\"" +Files: **/package.json + +Map findings: +- jest / @types/jest → Jest +- mocha / @types/mocha → Mocha +- chai / @types/chai → Chai +- jasmine / @types/jasmine → Jasmine +- vitest / @vitest → Vitest +``` + +### 4. Check Python Testing Frameworks +``` +Use Grep: "pytest|unittest|nose" +Files: **/requirements.txt, **/requirements-dev.txt, **/setup.py, **/pyproject.toml + +Map findings: +- pytest → pytest +- unittest → unittest +- nose → nose +``` + +### 5. Check Go Testing Frameworks +``` +Use Grep: "testify|ginkgo" +Files: **/go.mod + +Map findings: +- testify → testify +- ginkgo → Ginkgo +``` + +### 6. Count Test Files +``` +Use Glob to find test files: +- **/*Test.java, **/*Test.cs, **/*Tests.cs +- **/*.test.js, **/*.test.ts, **/*.spec.js, **/*.spec.ts +- **/test_*.py, **/*_test.go + +Count matching files for evidence +``` + +### 7. Analyze Test Patterns in Source +``` +Use Grep: "@Test|@TestMethod|\\[Fact\\]|\\[Theory\\]|describe\\(|it\\(|test\\(" +Files: **/*.{java,cs,js,ts,py} +Context: -B 1 -A 2 + +Check for common test annotations/decorators +``` + +## Confidence Determination + +### High Confidence +- ✅ Testing framework dependencies found in build files +- ✅ Test files present with matching annotations +- **Example**: "JUnit 5, Mockito detected in pom.xml; Found 23 test files" + +### Medium Confidence +- ⚠️ Test files found but no explicit framework dependency +- **Example**: "Test files found, framework inferred from annotations" + +### Low Confidence +- ⚠️ Few test files, no framework dependencies +- **Example**: "2 test-like files found, no framework detected" + +### Not Applicable +- ❌ No test files or testing dependencies +- **Example**: "No testing frameworks detected" + +## Output Format + +```json +{ + "input_name": "Testing Framework", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Frameworks summary}", + "confidence": "high|medium|low", + "evidence": [ + "{Build file framework references}", + "{Test file counts}", + "{Test annotation patterns}" + ], + "values": [ + "{Detected frameworks: JUnit 5, Mockito, xUnit, Jest, etc.}", + "{Test file count}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-version-information/SKILL.md b/.github/skills/fact-version-information/SKILL.md new file mode 100644 index 000000000..ee5894325 --- /dev/null +++ b/.github/skills/fact-version-information/SKILL.md @@ -0,0 +1,109 @@ +--- +name: fact-version-information +description: Extract application version from build files and configuration +--- + +# Version Information Analysis + +## Purpose +Identify the application version number from build descriptors, manifests, and configuration files. + +## Target Files/Locations +- **/pom.xml (<version>) +- **/*.csproj (<Version>) +- **/package.json (version field) +- **/build.gradle (version property) +- **/application.{properties,yml} (info.app.version) +- **/AssemblyInfo.cs ([assembly: AssemblyVersion]) + +## Example Patterns +- `<version>1.2.3</version>` (Maven) +- `"version": "2.0.0-beta"` (Node.js) +- `<Version>1.5.0</Version>` (.NET) +- `version = "3.1.4"` (Gradle) + +## Analysis Steps + +### 1. Check Build File Versions +``` +Maven (pom.xml): +Use Grep: "<version>" +Extract first non-parent version + +Gradle (build.gradle): +Use Grep: "^version\\s*=" +Extract: version = '1.2.3' + +Node.js (package.json): +Use Read and parse: { "version": "1.2.3" } + +.NET (*.csproj): +Use Grep: "<Version>" +Extract: <Version>1.2.3</Version> +``` + +### 2. Check Assembly Info +``` +.NET: +Use Grep: "AssemblyVersion|AssemblyFileVersion" +Files: **/AssemblyInfo.cs +Extract: [assembly: AssemblyVersion("1.2.3.4")] +``` + +### 3. Check Application Configuration +``` +Use Grep: "version:|app\\.version|info\\.app\\.version" +Files: **/application.{properties,yml}, **/appsettings.json +``` + +### 4. Check Git Tags +``` +Use Bash: git describe --tags --abbrev=0 +Latest tag often represents version +``` + +## Confidence Determination + +### High Confidence +- ✅ Version explicitly in build file +- ✅ Semantic versioning format (X.Y.Z) +- **Example**: "Application version: 1.2.3 from pom.xml and package.json" + +### Medium Confidence +- ⚠️ Version found but format unusual +- ⚠️ Snapshot/development version +- **Example**: "Version: 2.0.0-SNAPSHOT (development version)" + +### Low Confidence +- ⚠️ No version in files, inferred from git +- **Example**: "Version unclear, git tag suggests 1.0.0" + +### Not Applicable +- ❌ Versioning not used +- **Example**: "No version information found in project" + +## Output Format + +```json +{ + "input_name": "Version Information", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Version summary}", + "confidence": "high|medium|low", + "evidence": [ + "{Source file and location}", + "{Version format}", + "{Git tag if applicable}" + ], + "values": [ + "{Version number: X.Y.Z}", + "{Version type: release, snapshot, beta}", + "{Build/patch number if applicable}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-volume-mounts/SKILL.md b/.github/skills/fact-volume-mounts/SKILL.md new file mode 100644 index 000000000..65d8a57dd --- /dev/null +++ b/.github/skills/fact-volume-mounts/SKILL.md @@ -0,0 +1,96 @@ +--- +name: fact-volume-mounts +description: Identify persistent volume configurations for data storage +--- + +# Volume Mounts Analysis + +## Purpose +Identify persistent storage volumes configured for data persistence, configuration, and logs. + +## Target Files/Locations +- **/Dockerfile** (VOLUME instruction) +- **/docker-compose*.yml** (volumes section) +- **/k8s/**/*.yaml** (volumeMounts, volumes, PersistentVolumeClaim) + +## Example Patterns +- `VOLUME /data /logs` +- `volumes: - ./data:/app/data` +- `mountPath: /var/lib/postgresql/data` + +## Analysis Steps + +### 1. Check Dockerfile VOLUME +``` +Use Grep: "^VOLUME\\s+" +Files: **/Dockerfile +Extract volume paths +``` + +### 2. Analyze docker-compose Volumes +``` +Use Read: **/docker-compose*.yml +Look for volumes: section (both top-level and per-service) +Types: bind mounts, named volumes, tmpfs +``` + +### 3. Check Kubernetes Volumes +``` +Use Grep: "volumeMounts:|volumes:|persistentVolumeClaim" +Files: **/k8s/**/*.yaml +Context: -B 1 -A 5 +``` + +### 4. Categorize Volume Purposes +``` +Group by type: +- Data: /data, /var/lib/postgresql, /var/lib/mysql +- Logs: /logs, /var/log +- Config: /etc/config, /app/config +- Temp: /tmp, /var/tmp +``` + +## Confidence Determination + +### High Confidence +- ✅ Volumes explicitly configured +- ✅ Mount paths and purposes clear +- **Example**: "3 volumes: /data for database, /logs for application logs, /config for runtime config" + +### Medium Confidence +- ⚠️ VOLUME declared but mount points unclear +- **Example**: "Dockerfile declares VOLUME /data but docker-compose doesn't mount it" + +### Low Confidence +- ⚠️ No explicit volumes but app may use storage +- **Example**: "No volumes configured, data likely ephemeral" + +### Not Applicable +- ❌ Stateless application +- **Example**: "Stateless API with no persistent storage" + +## Output Format + +```json +{ + "input_name": "Volume Mounts", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Volumes summary}", + "confidence": "high|medium|low", + "evidence": [ + "{Dockerfile VOLUME}", + "{docker-compose volumes}", + "{K8s PVC/volumes}" + ], + "values": [ + "{Volume paths}", + "{Purposes: data, logs, config}", + "{Volume types: bind, named, PVC}" + ] + }, + "execution_time_seconds": {elapsed}, + "timestamp": "{ISO 8601}" +} +``` diff --git a/.github/skills/fact-xml-configs/SKILL.md b/.github/skills/fact-xml-configs/SKILL.md new file mode 100644 index 000000000..e49ea6bcf --- /dev/null +++ b/.github/skills/fact-xml-configs/SKILL.md @@ -0,0 +1,298 @@ +--- +name: fact-xml-configs +description: Analyze XML configuration files usage in the application +--- + +# XML Configuration Files Analysis + +## Purpose +Identify and catalog XML configuration files used by the application, including Spring context files, Hibernate configurations, web.xml, and other framework-specific XML files. This helps understand configuration management approach and migration requirements. + +## Target Files/Locations +- **Spring Framework**: **/applicationContext*.xml, **/spring-*.xml, **/*-context.xml, **/beans.xml +- **Hibernate ORM**: **/hibernate.cfg.xml, **/*.hbm.xml +- **Java EE/Jakarta EE**: **/web.xml, **/ejb-jar.xml, **/persistence.xml +- **MyBatis**: **/mybatis-config.xml, **/*-mapper.xml +- **Log4j**: **/log4j.xml, **/log4j2.xml +- **Maven**: **/pom.xml +- **Build configs**: **/build.xml (Ant), **/ivy.xml +- **Application configs**: **/config/**/*.xml, **/conf/**/*.xml + +## Example Patterns to Search +- Spring XML config with bean definitions +- Hibernate mapping files (.hbm.xml) +- MyBatis SQL mapper files +- web.xml servlet configurations +- persistence.xml JPA configurations +- log4j.xml or log4j2.xml logging configurations + +## Analysis Steps + +### 1. Search for Spring XML Configuration Files +``` +Use Glob tool to find Spring config files: +- **/applicationContext*.xml +- **/spring-*.xml +- **/*-context.xml +- **/beans.xml +- **/META-INF/spring/**/*.xml + +For each file found: +- Use Read tool to examine first 50 lines +- Check for <beans> root element +- Identify bean definitions, imports, property placeholders +- Note component-scan or annotation-config presence +``` + +### 2. Search for Hibernate Configuration Files +``` +Use Glob tool to find Hibernate files: +- **/hibernate.cfg.xml +- **/*.hbm.xml + +For each file: +- Read to identify database dialect +- Check for entity mappings +- Note session factory configuration +- Identify connection pool settings +``` + +### 3. Search for Java EE/Jakarta EE Deployment Descriptors +``` +Use Glob tool to find Java EE files: +- **/WEB-INF/web.xml +- **/META-INF/ejb-jar.xml +- **/META-INF/persistence.xml +- **/META-INF/application.xml + +Analyze each: +- Servlet/filter configurations (web.xml) +- JPA entity configurations (persistence.xml) +- EJB declarations (ejb-jar.xml) +``` + +### 4. Search for MyBatis Configuration Files +``` +Use Glob tool: +- **/mybatis-config.xml +- **/*-mapper.xml +- **/sqlmap/**/*.xml + +Check for: +- SQL mapper namespaces +- Select/insert/update/delete statements +- Result maps and parameter maps +``` + +### 5. Search for Logging XML Configurations +``` +Use Glob tool: +- **/log4j.xml +- **/log4j2.xml +- **/logback.xml + +Analyze: +- Appender configurations +- Logger level settings +- Output patterns +``` + +### 6. Count and Categorize All XML Files +``` +Use Bash tool to count XML files: +find . -type f -name "*.xml" -not -path "*/target/*" -not -path "*/.git/*" | wc -l + +Use Grep to identify XML configuration files (vs data files): +- Pattern: "<beans|<configuration|<hibernate-configuration|<web-app|<persistence" +- Files: **/*.xml +- Output mode: files_with_matches + +Categorize by framework: +- Spring: beans, context namespaces +- Hibernate: hibernate-configuration, hibernate-mapping +- Java EE: web-app, ejb-jar, persistence-unit +- MyBatis: mapper namespace +- Logging: log4j:configuration, configuration (logback) +``` + +### 7. Analyze XML Complexity and Size +``` +For each significant XML file: +- Use Read to get line count +- Check for external entity references +- Identify property placeholders (${...}) +- Note XSD/DTD schema references +- Check for profiles or conditional configurations +``` + +## Confidence Determination + +### High Confidence Criteria +Clear evidence of XML configuration usage with detailed findings: +- ✅ Multiple XML configuration files identified with specific purposes +- ✅ Framework-specific XML files present (Spring, Hibernate, etc.) +- ✅ Valid XML structure confirmed with proper root elements +- ✅ Bean/entity/mapper definitions clearly visible +- ✅ Relationship between files understood (imports, includes) + +**Examples**: +- "Found 23 Spring XML config files with 450+ bean definitions across applicationContext.xml and imported files" +- "Hibernate configuration with 15 .hbm.xml entity mapping files and hibernate.cfg.xml" +- "Web application with web.xml (Servlet 3.1) and 8 Spring context XML files" + +### Medium Confidence Criteria +Partial evidence or incomplete information: +- ⚠️ XML files found but unclear purpose or usage +- ⚠️ Config files present but may be legacy/unused +- ⚠️ XML files exist alongside annotation-based config (hybrid approach) +- ⚠️ Limited XML usage (only logging or build configs) +- ⚠️ XML files in resources but no clear loading mechanism + +**Examples**: +- "Spring XML files found but @Configuration classes also present (hybrid setup)" +- "Hibernate .hbm.xml files exist but JPA annotations also used" +- "XML files present but timestamps suggest not recently modified" + +### Low Confidence Criteria +Weak or ambiguous evidence: +- ⚠️ Only build tool XML files (pom.xml, build.xml) +- ⚠️ No framework-specific XML configurations +- ⚠️ XML files are data files, not configuration +- ⚠️ Test resources only, no production configs +- ⚠️ Commented-out or example XML files + +**Examples**: +- "Only pom.xml found, no application XML configs" +- "XML files in test resources only, production uses properties/YAML" +- "Sample XML files in documentation directory, not active configs" + +### Not Applicable Criteria +When XML configuration analysis doesn't apply: +- ❌ Pure annotation-based configuration (Spring Boot @Configuration) +- ❌ Configuration via properties/YAML files only +- ❌ Non-Java application (.NET, Node.js, Python) +- ❌ Library project with no configuration requirements +- ❌ Modern application using Java Config exclusively + +**Examples**: +- "Spring Boot application using only @Configuration classes and application.yml" +- ".NET Core application, XML config analysis not applicable" +- "Node.js application with JSON configuration only" + +## Output Format + +**CRITICAL**: Use the `write_assessment_result` tool (not just output JSON text). + +```json +{ + "input_name": "XML Configs", + "analysis_method": "LLM", + "status": "success|not_applicable", + "result": { + "finding": "{Clear 1-2 sentence summary of XML configuration usage}", + "confidence": "high|medium|low", + "evidence": [ + "{Number and types of XML files found}", + "{Specific file paths for major configs}", + "{Framework identifications from XML content}", + "{Configuration patterns observed}" + ], + "values": [ + "{Framework: Spring, Hibernate, MyBatis, etc.}", + "{File count by type}", + "{Key configuration file names}", + "{Schema versions or namespaces}" + ] + }, + "execution_time_seconds": {elapsed_time}, + "timestamp": "{ISO 8601 timestamp}" +} +``` + +**Finding Examples**: +- ✅ Good: "Application uses Spring XML configuration with 18 context files defining 250+ beans, including applicationContext.xml as root config" +- ✅ Good: "Hibernate-based persistence with hibernate.cfg.xml and 12 .hbm.xml entity mapping files for database layer" +- ✅ Good: "Web application with web.xml (Servlet 3.0), 5 Spring XML contexts, and MyBatis mapper files for SQL" +- ✅ Good: "No XML configuration detected - application uses Spring Boot with annotation-based @Configuration classes" +- ❌ Bad: "XML files found" +- ❌ Bad: "Configuration exists" + +**Evidence Examples**: +- ✅ Good: "applicationContext.xml at src/main/resources/ with 45 bean definitions and 3 imported context files" +- ✅ Good: "15 Hibernate mapping files (*.hbm.xml) in src/main/resources/mappings/ for entity persistence" +- ✅ Good: "web.xml at WEB-INF/ defines 8 servlets and 12 filters with Spring DispatcherServlet" +- ✅ Good: "23 MyBatis mapper XML files in resources/mappers/ with SQL definitions" +- ❌ Bad: "Found XML files in project" +- ❌ Bad: "Spring configuration present" + +## Error Handling + +### 1. No XML Configuration Found +- Report finding as "No XML configuration files detected" +- Set confidence to high if thorough search confirmed absence +- Note alternative config approaches if detected (annotations, properties, YAML) + +### 2. Mixed Configuration Approaches +- Report both XML and alternative approaches found +- Set confidence to medium +- List what's configured via XML vs annotations/properties +- Example: "Hybrid approach: Spring XML for legacy beans, @Configuration for new services" + +### 3. Too Many XML Files +- If >100 XML files found, categorize and sample +- Provide statistics by category +- Focus detailed analysis on framework config files +- Note if many are data files vs configuration files + +### 4. Tool Failures +- If Glob returns too many results, refine with more specific patterns +- If Read fails on corrupted XML, note the file but continue +- After 3 retries on critical operations, report partial results with caveats + +### 5. Invalid/Malformed XML +- Note files that appear to be XML but fail parsing +- Don't let malformed files block analysis +- Report count of valid vs invalid XML files found + +## Example Complete Analysis + +**Scenario**: Java Spring MVC application with XML-based configuration + +**Steps Executed**: +1. Glob for Spring XML: Found 18 files (applicationContext.xml + 17 imported) +2. Read applicationContext.xml: Confirmed Spring 4.3 with bean definitions +3. Glob for Hibernate: Found hibernate.cfg.xml and 12 .hbm.xml files +4. Glob for Java EE: Found web.xml in WEB-INF/ +5. Count total XML: 45 XML files in project (excluding target/) +6. Categorized: 18 Spring, 13 Hibernate, 1 Java EE, 8 MyBatis, 2 logging, 3 Maven + +**Result**: +```json +{ + "input_name": "XML Configs", + "analysis_method": "LLM", + "status": "success", + "result": { + "finding": "Application heavily uses XML configuration with 18 Spring context files, 13 Hibernate mappings, and Java EE web.xml for a traditional XML-based architecture", + "confidence": "high", + "evidence": [ + "applicationContext.xml at src/main/resources/ with <beans> root and 45 bean definitions", + "18 Spring XML files with imports: data-context.xml, security-context.xml, service-context.xml, etc.", + "hibernate.cfg.xml and 12 entity mapping files (*.hbm.xml) in resources/mappings/", + "web.xml at WEB-INF/web.xml defines DispatcherServlet and 8 filters (Servlet API 3.0)", + "8 MyBatis mapper XML files in resources/mappers/ with SQL queries", + "log4j.xml configuration for logging with 5 appenders" + ], + "values": [ + "Spring Framework (XML-based config, Spring 4.3 schema)", + "Hibernate ORM (12 .hbm.xml entity mappings)", + "Java EE Servlet 3.0 (web.xml)", + "MyBatis (8 mapper files)", + "45 total XML configuration files", + "Log4j XML configuration" + ] + }, + "execution_time_seconds": 32.8, + "timestamp": "2026-02-28T10:18:15Z" +} +``` diff --git a/.github/skills/generate-report-html/SKILL.md b/.github/skills/generate-report-html/SKILL.md new file mode 100644 index 000000000..a760bd87b --- /dev/null +++ b/.github/skills/generate-report-html/SKILL.md @@ -0,0 +1,54 @@ +--- +name: generate-report-html +description: Generate report.html from assessment report data by running the generate-report-html.py script +--- + +# Generate Report HTML + +Generate a self-contained `report.html` file from the assessment output by running the included Python script. + +## Execution + +Run the following command: + +```bash +python .github/modernize/assessment/engines/generate-report-html.py .github/modernize/assessment/reports/report-{reportId} +``` + +If the script is not at that path, look for it in the skill's own directory or copy it from there. The script file is `generate-report-html.py` bundled alongside this skill. + +Alternatively, if the script is available in the current working directory or skill resources: + +```bash +python generate-report-html.py .github/modernize/assessment/reports/report-{reportId} +``` + +## What the script does + +1. Reads `report.json` (Java/.NET) or `js-assessment-report.md` (JS/TS) from the report directory +2. Reads fact files from the `facts/` subdirectory (if present) +3. Generates `report.html` in the same directory + +## Prerequisites + +- Python 3 must be available in the environment +- The versioned report directory must already exist with `report.json` or the JS/TS report file +- Fact files should already be copied to the `facts/` subdirectory (if architecture analysis was enabled) + +## Input + +The script takes one argument: the path to the versioned report directory, e.g.: +`.github/modernize/assessment/reports/report-20240615143052` + +## Output + +`report.html` is written to the same directory as the input report. + +## Fallback + +If Python is not available or the script fails, you may generate report.html manually following the same structure as the local CLI renderer produces. The key requirements are: +- Self-contained HTML with embedded CSS and JavaScript +- SVG donut charts for issue summary +- Interactive expandable issue rows with file locations +- Fact tabs with Mermaid diagram support +- No "Back to aggregate dashboard" link diff --git a/.github/skills/generate-report-html/generate-report-html.py b/.github/skills/generate-report-html/generate-report-html.py new file mode 100644 index 000000000..122ee5070 --- /dev/null +++ b/.github/skills/generate-report-html/generate-report-html.py @@ -0,0 +1,1410 @@ +#!/usr/bin/env python3 +""" +Generates report.html from report.json (Java/.NET) or js-assessment-report.md (JS/TS). + +Usage: + python generate_report_html.py /path/to/.github/modernize/assessment/reports/report-{id} +""" + +import json +import math +import os +import re +import sys +from html import escape +from pathlib import Path + +# ── CSS (exact copy from reference) ────────────────────────────────────────── +CSS = r""":root { + --bg-primary: #ffffff; + --bg-card: #f8f9fa; + --bg-page: #f0f1f3; + --text-primary: #24292f; + --text-secondary: #57606a; + --text-muted: #6b7280; + --border-color: #d0d7de; + --border-light: #e1e4e8; + --link-color: #2563eb; + --color-mandatory: #E3008C; + --color-potential: #637CEF; + --color-optional: #A19F9D; + --font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Arial, sans-serif; + --font-mono: 'Cascadia Code', 'Consolas', 'Courier New', monospace; +} +* { box-sizing: border-box; margin: 0; padding: 0; } +body { font-family: var(--font-family); font-size: 13px; color: var(--text-primary); background: var(--bg-primary); line-height: 1.5; margin: 0; padding: 0; } +a { color: var(--link-color); text-decoration: none; } +a:hover { text-decoration: underline; } + +/* ── Page layout ───────────────────────────────────────────── */ +.main { margin: 0; padding: 24px 32px; background: var(--bg-primary); } +.back-link { color: var(--text-muted); font-size: 13px; margin-bottom: 16px; display: block; } +h1 { font-weight: 600; font-size: 20px; color: var(--text-primary); margin-bottom: 20px; } + +/* ── Report cards (matches VS Code .vscode-report-card) ──── */ +.report-card { background: var(--bg-card); border: 1px solid var(--border-light); border-radius: 4px; padding: 12px; margin-bottom: 20px; } +.report-card h2 { font-size: 14px; font-weight: 600; margin: 0; color: var(--text-primary); } +.report-card-body { margin-top: 16px; } + +/* ── Application Information (two-column) ────────────────── */ +.app-info-container { display: flex; gap: 8px; flex-wrap: wrap; } +.app-info-column { flex: 1; min-width: 200px; display: flex; flex-direction: column; gap: 8px; } +.app-info-row { display: flex; align-items: baseline; } +.app-info-label { width: 136px; flex-shrink: 0; font-weight: 600; color: var(--text-primary); } +.app-info-value { flex: 1; color: var(--text-primary); } + +/* ── Issue Summary (pie charts + legend) ─────────────────── */ +.cards-container { display: flex; align-items: stretch; gap: 20px; flex-wrap: wrap; } +.issue-summary-card { flex: 2; } +.domain-summary-container { display: flex; flex-wrap: wrap; gap: 20px; justify-content: space-around; } +.domain-summary-item { flex: 1; min-width: 120px; text-align: center; padding: 0 16px; border-right: 1px solid var(--border-light); } +.domain-summary-item:last-child { border-right: none; } +.domain-summary-item h3 { font-size: 13px; font-weight: 600; color: var(--text-primary); margin-top: 8px; } +.domain-summary-item .count { font-size: 12px; color: var(--text-secondary); } +.criticality-legend { display: flex; flex-wrap: wrap; gap: 15px; padding-top: 12px; justify-content: center; } +.criticality-legend .legend-item { display: flex; align-items: center; gap: 4px; font-size: 12px; color: var(--text-secondary); } +.legend-swatch { display: inline-block; width: 1em; height: 1em; border-radius: 2px; } + +/* ── Issue list tables ───────────────────────────────────── */ +.issue-section { margin-top: 20px; } +.issue-section h2 { font-size: 14px; font-weight: 600; margin-bottom: 8px; } +.issue-table { width: 100%; border-collapse: collapse; table-layout: fixed; min-width: 600px; } +.issue-table th { text-align: left; text-transform: uppercase; font-weight: 600; font-size: calc(13px * 0.875); padding: 6px 8px; background: var(--bg-card); border-bottom: 1px solid var(--border-color); color: var(--text-secondary); position: sticky; top: 0; z-index: 1; } +.issue-table td { padding: 12px 8px; border-bottom: 1px solid var(--border-light); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; } +.col-issue { width: 55%; } +.col-criticality { width: 25%; } +.col-storypoint { width: 20%; } + +/* ── Target dropdown ─────────────────────────────────────── */ +.target-select-container { margin: 4px 0 16px 0; display: flex; align-items: center; gap: 8px; } +.target-select-container label { font-weight: 600; color: var(--text-primary); font-size: 14px; } +.target-select-container select { font-family: var(--font-family); font-size: 13px; padding: 4px 8px; border: 1px solid var(--border-color); border-radius: 4px; background: var(--bg-primary); color: var(--text-primary); cursor: pointer; } + +/* ── Filter bar ──────────────────────────────────────────── */ +.filter-bar { display: flex; flex-wrap: wrap; gap: 16px; margin: 16px 0 8px 0; align-items: center; } +.multi-select { position: relative; display: inline-block; min-width: 140px; } +.multi-select-btn { font-family: var(--font-family); font-size: 13px; padding: 4px 24px 4px 8px; border: 1px solid var(--border-color); border-radius: 4px; background: var(--bg-primary); color: var(--text-primary); cursor: pointer; width: 100%; text-align: left; position: relative; white-space: nowrap; } +.multi-select-btn::after { content: '\25BE'; position: absolute; right: 8px; top: 50%; transform: translateY(-50%); font-size: 11px; color: var(--text-secondary); } +.multi-select-dropdown { display: none; position: absolute; top: 100%; left: 0; min-width: 100%; background: var(--bg-primary); border: 1px solid var(--border-color); border-radius: 4px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); z-index: 100; margin-top: 2px; } +.multi-select.open .multi-select-dropdown { display: block; } +.multi-select-option { display: flex; align-items: center; gap: 6px; padding: 5px 10px; cursor: pointer; font-size: 13px; white-space: nowrap; } +.multi-select-option:hover { background: var(--bg-secondary); } +.multi-select-option input[type=checkbox] { margin: 0; cursor: pointer; } +.clear-filters { font-size: 12px; color: var(--link-color); cursor: pointer; margin-left: 4px; } + +/* ── Criticality labels (colored square + text) ──────────── */ +.crit-label { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; white-space: nowrap; } +.crit-square { display: inline-block; width: 10px; height: 10px; border-radius: 2px; flex-shrink: 0; } +.crit-square-mandatory { background: var(--color-mandatory); } +.crit-square-potential { background: var(--color-potential); } +.crit-square-optional { background: var(--color-optional); } + +/* ── Expandable rows ─────────────────────────────────────── */ +.expand-btn { background: none; border: none; cursor: pointer; width: 20px; height: 20px; display: inline-flex; align-items: center; justify-content: center; color: var(--text-muted); font-size: 11px; transition: transform 0.15s; padding: 0; vertical-align: middle; flex-shrink: 0; } +.expand-btn.open { transform: rotate(90deg); } +.issue-title-cell { display: flex; align-items: center; gap: 4px; } +.detail-row td { padding: 0; border-bottom: 1px solid var(--border-light); white-space: normal; overflow: visible; text-overflow: clip; } +.detail-content { display: flex; gap: 0; padding: 8px 8px 8px 32px; min-width: 0; } +.file-list { flex: 0 0 50%; min-width: 0; overflow: hidden; } +.file-list table { width: 100%; border-collapse: collapse; } +.file-list th { font-size: calc(13px * 0.875); text-transform: uppercase; font-weight: 600; color: var(--text-secondary); padding: 6px 8px; border-bottom: 1px solid var(--border-color); text-align: left; height: 32px; } +.file-list td { font-size: 13px; padding: 6px 8px; border-bottom: 1px solid var(--border-light); color: var(--text-primary); } +.file-list .file-path { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 300px; display: block; } +.file-list .position { font-family: var(--font-mono); font-size: 12px; color: var(--text-secondary); } +.explanation-panel { flex: 1; padding-left: 16px; min-width: 0; overflow-wrap: break-word; word-wrap: break-word; } +.explanation-panel h4 { font-size: calc(13px * 0.875); text-transform: uppercase; font-weight: 600; color: var(--text-secondary); padding: 6px 0; border-bottom: 1px solid var(--border-color); height: 32px; margin: 0; } +.explanation-panel p { font-size: 13px; padding: 8px 0; color: var(--text-primary); line-height: 1.6; white-space: normal; word-break: break-word; } + +/* ── Experimental badge ──────────────────────────────────── */ +.badge-experimental { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; background: #fef9c3; color: #854d0e; vertical-align: middle; margin-left: 8px; cursor: default; } + +/* ── Footer ──────────────────────────────────────────────── */ +.footer { text-align: center; margin-top: 24px; color: var(--text-muted); font-size: 13px; } + +/* ── Responsive ──────────────────────────────────────────── */ +@media (max-width: 800px) { + .main { padding: 16px; } + .app-info-container { flex-direction: column; } + .domain-summary-container { flex-direction: column; } + .domain-summary-item { border-right: none; border-bottom: 1px solid var(--border-light); padding-bottom: 12px; } + .detail-content { flex-direction: column; } + .file-list { flex: none; width: 100%; } + .explanation-panel { padding-left: 0; padding-top: 8px; } +} +/* ── Tab navigation ──────────────────────────────────────── */ +.tab-nav { display: flex; gap: 0; border-bottom: 1px solid var(--border-color); margin-bottom: 28px; flex-wrap: wrap; } +.tab-btn { background: none; border: none; border-bottom: 2px solid transparent; padding: 10px 18px; font-size: 13px; font-family: var(--font-family); color: var(--text-secondary); cursor: pointer; margin-bottom: -1px; white-space: nowrap; position: relative; } +.tab-btn:hover { color: var(--text-primary); } +.tab-btn.active { color: var(--link-color); border-bottom-color: var(--link-color); font-weight: 600; } +.tab-panel { display: none; } +.tab-panel.active { display: block; } + +/* ── Tooltip on tab ──────────────────────────────────────── */ +.tab-btn[data-tooltip]::after { content: attr(data-tooltip); position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%); background: #24292f; color: #ffffff; font-size: 12px; font-weight: 400; padding: 6px 10px; border-radius: 4px; white-space: normal; width: max-content; max-width: 260px; text-align: center; pointer-events: none; opacity: 0; transition: opacity 0.15s; z-index: 100; } +.tab-btn[data-tooltip]:hover::after { opacity: 1; } + +/* ── Fact content — prose column ─────────────────────────── */ +.fact-content { max-width: 860px; margin: 0 auto; line-height: 1.75; color: var(--text-primary); font-size: 14px; } +.fact-content h1 { font-size: 22px; font-weight: 700; margin: 0 0 6px 0; color: var(--text-primary); letter-spacing: -0.01em; } +.fact-content h1 + p { margin-top: 6px; color: var(--text-secondary); font-size: 14px; margin-bottom: 24px; } +.fact-content h2 { font-size: 17px; font-weight: 700; margin: 36px 0 12px 0; padding-bottom: 6px; border-bottom: 1px solid var(--border-color); color: var(--text-primary); letter-spacing: -0.01em; } +.fact-content h3 { font-size: 14px; font-weight: 700; margin: 24px 0 8px 0; color: var(--text-primary); text-transform: uppercase; letter-spacing: 0.04em; font-size: 12px; color: var(--text-secondary); } +.fact-content p { margin: 10px 0; color: var(--text-primary); } +.fact-content ul, .fact-content ol { margin: 10px 0 10px 22px; } +.fact-content li { margin: 5px 0; } +.fact-content a { color: var(--link-color); } +.fact-content a:hover { text-decoration: underline; } + +/* ── Fact tables ─────────────────────────────────────────── */ +.table-wrap { overflow-x: auto; margin: 16px 0; } +.fact-content table { border-collapse: collapse; width: 100%; margin: 16px 0; font-size: 13px; border-radius: 6px; overflow: hidden; border: 1px solid var(--border-color); } +.fact-content thead { background: var(--bg-card); } +.fact-content th { text-align: left; font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; padding: 8px 14px; border-bottom: 1px solid var(--border-color); color: var(--text-secondary); } +.fact-content td { padding: 9px 14px; border-bottom: 1px solid var(--border-light); vertical-align: top; color: var(--text-primary); } +.fact-content tr:last-child td { border-bottom: none; } +.fact-content tbody tr:hover { background: #f6f8fa; } + +/* ── Fact code ───────────────────────────────────────────── */ +.fact-content code { font-family: var(--font-mono); font-size: 12px; background: #f0f1f3; padding: 2px 6px; border-radius: 4px; color: #c7254e; } +.fact-content pre { background: #f6f8fa; border: 1px solid var(--border-light); border-radius: 6px; padding: 14px 16px; overflow-x: auto; margin: 14px 0; } +.fact-content pre code { background: none; color: var(--text-primary); padding: 0; border-radius: 0; } +.fact-content hr { border: none; border-top: 1px solid var(--border-light); margin: 28px 0; } +.fact-content strong { font-weight: 600; } +.fact-content em { font-style: italic; } +.fact-content blockquote { border-left: 3px solid var(--border-color); margin: 12px 0; padding: 4px 16px; color: var(--text-secondary); } +.fact-unavailable { padding: 32px 0; } +.fact-unavailable h3 { font-size: 16px; font-weight: 600; color: var(--text-primary); text-transform: none; letter-spacing: 0; margin: 0 0 12px 0; } +.fact-unavailable p { margin: 0 0 8px 0; color: var(--text-secondary); } +.fact-unavailable ol, .fact-unavailable ul { margin: 0 0 0 22px; color: var(--text-primary); } +.fact-unavailable li { margin: 6px 0; } + +/* ── Mermaid diagram card ─────────────────────────────────── */ +.mermaid { background: var(--bg-card); border: 1px solid var(--border-light); border-radius: 8px; padding: 24px 16px; margin: 20px 0; overflow-x: auto; text-align: center; cursor: pointer; position: relative; } +.mermaid:hover { border-color: var(--border-color); } +.mermaid svg { max-width: 100%; height: auto !important; display: inline-block; } +.mermaid-zoom-hint { position: absolute; top: 8px; right: 10px; font-size: 11px; color: var(--text-muted); opacity: 0; transition: opacity 0.15s; pointer-events: none; } +.mermaid:hover .mermaid-zoom-hint { opacity: 1; } + +/* ── Diagram lightbox ────────────────────────────────────── */ +.diagram-lightbox { display: none; position: fixed; inset: 0; z-index: 1000; background: rgba(0,0,0,0.7); backdrop-filter: blur(2px); align-items: center; justify-content: center; cursor: zoom-out; padding: 48px 32px; } +.diagram-lightbox.open { display: flex; } +.diagram-lightbox-inner { background: #ffffff; border-radius: 10px; padding: 32px; width: 88vw; max-height: 88vh; overflow: hidden; cursor: default; box-shadow: 0 24px 64px rgba(0,0,0,0.35); display: flex; align-items: center; justify-content: center; } +.diagram-lightbox-inner svg { width: 100% !important; height: auto !important; max-height: 76vh; display: block; } +.diagram-lightbox-close { position: fixed; top: 20px; right: 24px; background: #ffffff; border: none; border-radius: 50%; width: 36px; height: 36px; font-size: 18px; cursor: pointer; display: flex; align-items: center; justify-content: center; color: var(--text-primary); box-shadow: 0 2px 8px rgba(0,0,0,0.2); z-index: 1001; } +.diagram-lightbox-close:hover { background: var(--bg-card); } +.diagram-lightbox-hint { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.55); color: #ffffff; font-size: 12px; padding: 6px 14px; border-radius: 20px; pointer-events: none; white-space: nowrap; z-index: 1001; }""" + +# ── Mermaid head script ────────────────────────────────────────────────────── +MERMAID_HEAD_SCRIPT = """<script> +window.__mermaidPending = []; +window.__mermaidReady = false; +window.__renderMermaidIn = function(container) { + if (container) { window.__mermaidPending.push(container); } +}; +</script> +<script type="module"> +import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs'; +mermaid.initialize({ startOnLoad: false, theme: 'default', flowchart: { useMaxWidth: true }, sequence: { useMaxWidth: true } }); + +window.__renderMermaidIn = function(container) { + var nodes = container ? container.querySelectorAll('.mermaid:not([data-processed])') : []; + if (nodes.length > 0) { + mermaid.run({ nodes: Array.from(nodes) }).then(function() { + nodes.forEach(function(node) { + if (!node.querySelector('.mermaid-zoom-hint')) { + var hint = document.createElement('span'); + hint.className = 'mermaid-zoom-hint'; + hint.textContent = 'Click to expand'; + node.appendChild(hint); + } + }); + }); + } +}; + +var drained = new Set(); +(window.__mermaidPending || []).forEach(function(c) { + if (!drained.has(c)) { drained.add(c); window.__renderMermaidIn(c); } +}); +window.__mermaidPending = []; +window.__mermaidReady = true; + +function renderActivePanels() { + document.querySelectorAll('.tab-panel.active').forEach(function(p) { + if (!drained.has(p)) { drained.add(p); window.__renderMermaidIn(p); } + }); +} +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', renderActivePanels); +} else { + renderActivePanels(); +} +</script>""" + +# ── JavaScript ─────────────────────────────────────────────────────────────── +MAIN_SCRIPT = r"""function toggleRow(rowId, triggerRow) { + var detail = document.getElementById(rowId); + var btn = document.getElementById('btn-' + rowId); + if (!detail) return; + var visible = detail.style.display !== 'none'; + detail.style.display = visible ? 'none' : 'table-row'; + if (btn) btn.classList.toggle('open', !visible); +} +(function() { + var nav = document.querySelector('.tab-nav'); + if (!nav) return; + nav.addEventListener('click', function(e) { + var btn = e.target.closest('.tab-btn'); + if (!btn) return; + var targetId = btn.getAttribute('data-tab'); + document.querySelectorAll('.tab-btn').forEach(function(b) { b.classList.remove('active'); }); + document.querySelectorAll('.tab-panel').forEach(function(p) { p.classList.remove('active'); }); + btn.classList.add('active'); + var panel = document.getElementById(targetId); + if (panel) { + panel.classList.add('active'); + if (window.__mermaidReady) { window.__renderMermaidIn(panel); } + } + }); +})(); + +// ── Diagram lightbox ──────────────────────────────────────── +(function() { + var lightbox = document.getElementById('diagram-lightbox'); + var inner = document.getElementById('diagram-lightbox-inner'); + var closeBtn = document.getElementById('diagram-lightbox-close'); + if (!lightbox || !inner || !closeBtn) return; + + var scale = 1; + var translateX = 0; + var translateY = 0; + var isDragging = false; + var dragStartX = 0; + var dragStartY = 0; + + function applyTransform() { + var svg = inner.querySelector('svg'); + if (svg) { + svg.style.transform = 'translate(' + translateX + 'px, ' + translateY + 'px) scale(' + scale + ')'; + svg.style.transformOrigin = 'center center'; + svg.style.transition = 'none'; + } + } + + function resetTransform() { + scale = 1; translateX = 0; translateY = 0; + applyTransform(); + } + + function openLightbox(svgEl) { + inner.innerHTML = svgEl.outerHTML; + var injected = inner.querySelector('svg'); + if (injected) { + injected.removeAttribute('width'); + injected.removeAttribute('height'); + injected.style.cursor = 'grab'; + } + resetTransform(); + lightbox.classList.add('open'); + document.body.style.overflow = 'hidden'; + } + + function closeLightbox() { + lightbox.classList.remove('open'); + inner.innerHTML = ''; + document.body.style.overflow = ''; + resetTransform(); + } + + inner.addEventListener('wheel', function(e) { + e.preventDefault(); + var delta = e.deltaY > 0 ? 0.9 : 1.1; + scale = Math.min(Math.max(scale * delta, 0.3), 8); + applyTransform(); + }, { passive: false }); + + inner.addEventListener('mousedown', function(e) { + if (e.button !== 0) return; + isDragging = true; + dragStartX = e.clientX - translateX; + dragStartY = e.clientY - translateY; + var svg = inner.querySelector('svg'); + if (svg) { svg.style.cursor = 'grabbing'; } + e.preventDefault(); + }); + + document.addEventListener('mousemove', function(e) { + if (!isDragging) return; + translateX = e.clientX - dragStartX; + translateY = e.clientY - dragStartY; + applyTransform(); + }); + + document.addEventListener('mouseup', function() { + if (!isDragging) return; + isDragging = false; + var svg = inner.querySelector('svg'); + if (svg) { svg.style.cursor = 'grab'; } + }); + + inner.addEventListener('dblclick', function() { resetTransform(); }); + + document.addEventListener('click', function(e) { + var card = e.target.closest('.mermaid'); + if (!card) return; + if (e.target.closest('.diagram-lightbox-close')) return; + var svg = card.querySelector('svg'); + if (svg) { openLightbox(svg); } + }); + + closeBtn.addEventListener('click', closeLightbox); + + lightbox.addEventListener('click', function(e) { + if (e.target === lightbox) { closeLightbox(); } + }); + + document.addEventListener('keydown', function(e) { + if (e.key === 'Escape') { closeLightbox(); } + }); +})(); + +var _currentTarget = ''; + +function toggleMultiSelect(id) { + var el = document.getElementById(id); + if (!el) return; + document.querySelectorAll('.multi-select.open').forEach(function(ms) { if (ms.id !== id) ms.classList.remove('open'); }); + el.classList.toggle('open'); +} + +document.addEventListener('click', function(e) { + if (!e.target.closest('.multi-select')) { + document.querySelectorAll('.multi-select.open').forEach(function(ms) { ms.classList.remove('open'); }); + } +}); + +function getMultiSelectValues(msId) { + var el = document.getElementById(msId); + if (!el) return []; + var checked = el.querySelectorAll('input[type=checkbox]:checked'); + var vals = []; + for (var i = 0; i < checked.length; i++) vals.push(checked[i].value); + return vals; +} + +function onMultiSelectChange(msId, label) { + var el = document.getElementById(msId); + if (!el) return; + var vals = getMultiSelectValues(msId); + var total = el.querySelectorAll('input[type=checkbox]').length; + var btn = el.querySelector('.multi-select-btn'); + if (btn) { + btn.textContent = vals.length > 0 && vals.length < total ? label + ' (' + vals.length + ')' : label; + } + applyFilters(); +} + +function applyFilters() { + var selectedDomains = getMultiSelectValues('domain-ms'); + var selectedCrits = getMultiSelectValues('criticality-ms'); + + var sections = document.querySelectorAll('.issue-section'); + for (var i = 0; i < sections.length; i++) { + var domain = sections[i].getAttribute('data-domain'); + sections[i].style.display = (selectedDomains.length === 0 || selectedDomains.indexOf(domain) >= 0) ? '' : 'none'; + } + + var rows = document.querySelectorAll('.issue-row'); + for (var i = 0; i < rows.length; i++) { + var row = rows[i]; + var section = row.closest('.issue-section'); + if (section && section.style.display === 'none') { + row.style.display = 'none'; + var next = row.nextElementSibling; + if (next && next.classList.contains('detail-row')) next.style.display = 'none'; + continue; + } + + var show = true; + if (_currentTarget && row.hasAttribute('data-targets')) { + var targetsStr = row.getAttribute('data-targets'); + if (targetsStr && targetsStr !== '{}') { + try { + var targets = JSON.parse(targetsStr); + if (Object.keys(targets).length > 0 && !targets[_currentTarget]) { + show = false; + } + } catch(e) {} + } + } + + if (show && selectedCrits.length > 0) { + var crit = row.getAttribute('data-criticality'); + if (selectedCrits.indexOf(crit) < 0) show = false; + } + + row.style.display = show ? '' : 'none'; + var nextRow = row.nextElementSibling; + if (nextRow && nextRow.classList.contains('detail-row')) { + if (!show) nextRow.style.display = 'none'; + } + } +} + +function clearAllFilters() { + document.querySelectorAll('.multi-select input[type=checkbox]').forEach(function(cb) { cb.checked = false; }); + var domainBtn = document.querySelector('#domain-ms .multi-select-btn'); + if (domainBtn) domainBtn.textContent = 'Domain'; + var critBtn = document.querySelector('#criticality-ms .multi-select-btn'); + if (critBtn) critBtn.textContent = 'Criticality'; + applyFilters(); +} + +function onTargetChange(targetId) { + _currentTarget = targetId; + var rows = document.querySelectorAll('.issue-row[data-targets]'); + var totalEffort = 0; + var domainCrits = {}; + + for (var i = 0; i < rows.length; i++) { + var row = rows[i]; + var targets = JSON.parse(row.getAttribute('data-targets')); + var section = row.closest('.issue-section'); + var domain = section ? section.getAttribute('data-domain') : ''; + + if (!targets || Object.keys(targets).length === 0) { + var sp = parseFloat(row.querySelector('.sp-cell').textContent) || 0; + totalEffort += sp; + if (!domainCrits[domain]) domainCrits[domain] = {mandatory:0,potential:0,optional:0}; + var crit = row.getAttribute('data-criticality') || 'optional'; + domainCrits[domain][crit] = (domainCrits[domain][crit] || 0) + 1; + continue; + } + + var override = targets[targetId]; + if (!override) { + continue; + } + + var newSeverity = (override.severity || row.getAttribute('data-default-severity') || '').toLowerCase(); + row.setAttribute('data-criticality', newSeverity); + var critCell = row.querySelector('.crit-cell'); + if (critCell) { + var critText = newSeverity === 'mandatory' ? 'Mandatory' : newSeverity === 'potential' ? 'Potential' : 'Optional'; + var cssClass = newSeverity === 'mandatory' ? 'crit-square-mandatory' : newSeverity === 'potential' ? 'crit-square-potential' : 'crit-square-optional'; + critCell.innerHTML = '<span class="crit-label"><span class="crit-square ' + cssClass + '"></span>' + critText + '</span>'; + } + + var newEffort = override.effort !== undefined ? override.effort : parseFloat(row.getAttribute('data-default-effort')) || 0; + var spCell = row.querySelector('.sp-cell'); + if (spCell) spCell.textContent = newEffort; + totalEffort += newEffort; + + if (!domainCrits[domain]) domainCrits[domain] = {mandatory:0,potential:0,optional:0}; + domainCrits[domain][newSeverity] = (domainCrits[domain][newSeverity] || 0) + 1; + } + + var secSection = document.querySelector('.issue-section[data-domain="security"]'); + if (secSection) { + var secRows = secSection.querySelectorAll('.issue-row'); + for (var j = 0; j < secRows.length; j++) { + if (!secRows[j].hasAttribute('data-targets') || secRows[j].getAttribute('data-targets') === '') { + var sp = parseFloat(secRows[j].querySelector('.sp-cell').textContent) || 0; + totalEffort += sp; + if (!domainCrits['security']) domainCrits['security'] = {mandatory:0,potential:0,optional:0}; + var sc = secRows[j].getAttribute('data-criticality') || 'optional'; + domainCrits['security'][sc] = (domainCrits['security'][sc] || 0) + 1; + } + } + } + + var label = totalEffort < 20 ? 'S' : totalEffort < 50 ? 'M' : totalEffort < 100 ? 'L' : 'XL'; + var effortEl = document.getElementById('effort-display'); + if (effortEl) effortEl.textContent = label + ' (total story points: ' + totalEffort + ')'; + + updateDonuts(domainCrits); + applyFilters(); +} + +function updateDonuts(domainCrits) { + var container = document.getElementById('donut-container'); + if (!container) return; + container.innerHTML = ''; + var domainNames = {'cloud-readiness':'Cloud Readiness','java-upgrade':'Java Upgrade','security':'Security'}; + var domainOrder = ['cloud-readiness','java-upgrade','security']; + for (var d = 0; d < domainOrder.length; d++) { + var key = domainOrder[d]; + var c = domainCrits[key]; + if (!c || (c.mandatory + c.potential + c.optional) === 0) continue; + var total = c.mandatory + c.potential + c.optional; + var div = document.createElement('div'); + div.className = 'domain-summary-item'; + div.innerHTML = buildDonutSvg(c.mandatory, c.potential, c.optional) + + '<h3>' + domainNames[key] + '</h3>' + + '<div class="count">' + total + ' issue' + (total !== 1 ? 's' : '') + '</div>'; + container.appendChild(div); + } +} + +function buildDonutSvg(m, p, o) { + var total = m + p + o; + if (total === 0) return '<svg viewBox="0 0 100 100" width="100" height="100" style="display:block;margin:0 auto;"><circle cx="50" cy="50" r="35" fill="none" stroke="#d0d7de" stroke-width="15" opacity="0.5"/></svg>'; + var r = 35, circ = 2 * Math.PI * r, offset = 0; + var svg = '<svg viewBox="0 0 100 100" width="100" height="100" style="display:block;margin:0 auto;transform:rotate(-90deg);">'; + var segs = [{c:m,color:'var(--color-mandatory)'},{c:p,color:'var(--color-potential)'},{c:o,color:'var(--color-optional)'}]; + for (var i = 0; i < segs.length; i++) { + if (segs[i].c === 0) continue; + var segLen = (segs[i].c / total) * circ; + var gap = total > segs[i].c ? 1.5 : 0; + var drawLen = Math.max(segLen - gap, 0.5); + svg += '<circle cx="50" cy="50" r="' + r + '" fill="none" stroke="' + segs[i].color + '" stroke-width="15" stroke-dasharray="' + drawLen.toFixed(2) + ' ' + circ.toFixed(2) + '" stroke-dashoffset="' + (-offset).toFixed(2) + '"/>'; + offset += segLen; + } + svg += '</svg>'; + return svg; +}""" + + +# ── Helpers ────────────────────────────────────────────────────────────────── + +def get_severity_rank(severity): + if not severity: + return 99 + s = severity.strip().lower() + return {"mandatory": 0, "potential": 1, "optional": 2}.get(s, 99) + + +def build_donut_svg(mandatory, potential, optional): + total = mandatory + potential + optional + if total == 0: + return ('<svg viewBox="0 0 100 100" width="100" height="100" style="display:block;margin:0 auto;">' + '<circle cx="50" cy="50" r="35" fill="none" stroke="#d0d7de" stroke-width="15" opacity="0.5"/>' + '</svg>\n') + r = 35 + circ = 2 * math.pi * r + segments = [ + (mandatory, "var(--color-mandatory)"), + (potential, "var(--color-potential)"), + (optional, "var(--color-optional)"), + ] + svg = '<svg viewBox="0 0 100 100" width="100" height="100" style="display:block;margin:0 auto;transform:rotate(-90deg);">\n' + offset = 0.0 + for count, color in segments: + if count == 0: + continue + seg_len = (count / total) * circ + gap = 1.5 if total > count else 0 + draw_len = max(seg_len - gap, 0.5) + svg += (f' <circle cx="50" cy="50" r="{r}" fill="none" stroke="{color}" ' + f'stroke-width="15" stroke-dasharray="{draw_len:.2f} {circ:.2f}" ' + f'stroke-dashoffset="{-offset:.2f}"/>\n') + offset += seg_len + svg += '</svg>\n' + return svg + + +def simple_markdown_to_html(md): + """Convert rule description markdown to inline HTML.""" + if not md: + return "" + html = escape(md) + # Code blocks + html = re.sub(r'```\w*\r?\n([\s\S]*?)```', r'<pre><code>\1</code></pre>', html) + # Inline code + html = re.sub(r'`([^`]+)`', r'<code>\1</code>', html) + # Bold + html = re.sub(r'\*\*(.+?)\*\*', r'<strong>\1</strong>', html) + # Links + def replace_link(m): + text, url = m.group(1), m.group(2) + return f'<a href="{url}" target="_blank" rel="noopener noreferrer">{text}</a>' + html = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', replace_link, html) + + def apply_italic(text): + return re.sub(r'\*(.+?)\*', r'<em>\1</em>', text) + + # Process line-by-line for block structures + lines = re.split(r'\r?\n', html) + result = [] + in_list = False + in_paragraph = False + + for line in lines: + # List item + list_match = re.match(r'^[\-\*]\s+(.+)', line) + if list_match: + if in_paragraph: + result.append('</p>') + in_paragraph = False + if not in_list: + result.append('<ul>') + in_list = True + content = apply_italic(list_match.group(1)) + result.append(f'<li>{content}</li>') + continue + + # End list if needed + if in_list: + result.append('</ul>') + in_list = False + + # Blank line + if not line.strip(): + if in_paragraph: + result.append('</p>') + in_paragraph = False + continue + + # Regular text + text_content = apply_italic(line) + if not in_paragraph: + result.append('<p>') + in_paragraph = True + else: + result.append('<br/>') + result.append(text_content) + + if in_list: + result.append('</ul>') + if in_paragraph: + result.append('</p>') + + return ''.join(result) + + +def markdown_to_fact_html(md): + """Convert a fact .md file to HTML for the fact-content div.""" + if not md: + return "" + lines = md.split('\n') + out = [] + i = 0 + in_list = None # 'ul' or 'ol' + in_table = False + table_lines = [] + + def flush_table(): + nonlocal in_table, table_lines + if not table_lines: + return + # Parse markdown table + header = table_lines[0] + # table_lines[1] is the separator + rows = table_lines[2:] if len(table_lines) > 2 else [] + cols = [c.strip() for c in header.strip('|').split('|')] + out.append('<div class="table-wrap"><table><thead><tr>') + out.append(''.join(f'<th>{inline_md(c)}</th>' for c in cols)) + out.append('</tr></thead><tbody>') + for row in rows: + cells = [c.strip() for c in row.strip('|').split('|')] + out.append('<tr>' + ''.join(f'<td>{inline_md(c)}</td>' for c in cells) + '</tr>') + out.append('</tbody></table></div>') + table_lines = [] + in_table = False + + def flush_list(): + nonlocal in_list + if in_list: + out.append(f'</{in_list}>') + in_list = None + + def inline_md(text): + """Convert inline markdown.""" + t = text + t = re.sub(r'`([^`]+)`', r'<code>\1</code>', t) + t = re.sub(r'\*\*(.+?)\*\*', r'<strong>\1</strong>', t) + t = re.sub(r'\*(.+?)\*', r'<em>\1</em>', t) + t = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'<a href="\2">\1</a>', t) + return t + + while i < len(lines): + line = lines[i] + + # Mermaid code block + if line.strip().startswith('```mermaid'): + flush_list() + flush_table() + i += 1 + mermaid_lines = [] + while i < len(lines) and not lines[i].strip().startswith('```'): + mermaid_lines.append(lines[i]) + i += 1 + i += 1 # skip closing ``` + out.append('<div class="mermaid">') + out.append('\n'.join(mermaid_lines)) + out.append('</div>') + continue + + # Regular code block + if line.strip().startswith('```'): + flush_list() + flush_table() + i += 1 + code_lines = [] + while i < len(lines) and not lines[i].strip().startswith('```'): + code_lines.append(escape(lines[i])) + i += 1 + i += 1 + out.append('<pre><code>') + out.append('\n'.join(code_lines)) + out.append('</code></pre>') + continue + + # Table detection + if '|' in line and i + 1 < len(lines) and re.match(r'^\s*\|?[\s\-:|]+\|', lines[i + 1]): + flush_list() + if not in_table: + in_table = True + table_lines = [] + table_lines.append(line) + i += 1 + continue + if in_table and '|' in line: + table_lines.append(line) + i += 1 + continue + if in_table: + flush_table() + + # Headers + m = re.match(r'^(#{1,3})\s+(.+)', line) + if m: + flush_list() + level = len(m.group(1)) + text = m.group(2).strip() + tag = f'h{level}' + hid = re.sub(r'[^a-z0-9]+', '-', text.lower()).strip('-') + out.append(f'<{tag} id="{hid}">{inline_md(text)}</{tag}>') + i += 1 + continue + + # Unordered list + m = re.match(r'^[\-\*]\s+(.+)', line) + if m: + flush_table() + if in_list != 'ul': + flush_list() + in_list = 'ul' + out.append('<ul>') + out.append(f'<li>{inline_md(m.group(1))}</li>') + i += 1 + continue + + # Ordered list + m = re.match(r'^\d+\.\s+(.+)', line) + if m: + flush_table() + if in_list != 'ol': + flush_list() + in_list = 'ol' + out.append('<ol>') + out.append(f'<li>{inline_md(m.group(1))}</li>') + i += 1 + continue + + # Blank line + if not line.strip(): + flush_list() + i += 1 + continue + + # Paragraph + flush_list() + out.append(f'<p>{inline_md(line)}</p>') + i += 1 + + flush_list() + flush_table() + return '\n'.join(out) + + +def is_dotnet_component(language): + if not language or language.strip().upper() == "N/A": + return True + return ".net" in language.lower() or "c#" in language.lower() + + +# ── Fact tabs catalog ──────────────────────────────────────────────────────── +FACT_TABS = [ + ("tab-arch", "Architecture", "How the app's layers connect — from UI to data.", "architecture-diagram.md"), + ("tab-api", "API Contracts", "Endpoints exposed by this app and how services communicate.", "api-service-contracts.md"), + ("tab-config", "Configuration", "Environment settings, runtime profiles, and deployment manifests.", "configuration-inventory.md"), + ("tab-workflows", "Business Workflows", "Domain entities and the core flows users move through.", "business-workflows.md"), + ("tab-deps", "Dependencies", "Libraries and frameworks this app depends on, grouped by category.", "dependency-map.md"), + ("tab-data", "Data Model", "Database schema, entity relationships, and persistence behavior.", "data-architecture.md"), +] + + +# ── Main generation logic ──────────────────────────────────────────────────── + +def generate_from_report_json(report_dir): + """Generate report.html from report.json (Java/.NET).""" + report_json_path = os.path.join(report_dir, "report.json") + if not os.path.isfile(report_json_path): + print(f"Error: report.json not found at {report_json_path}", file=sys.stderr) + sys.exit(1) + + with open(report_json_path, 'r', encoding='utf-8') as f: + root = json.load(f) + + # Parse metadata + metadata = root.get("metadata", {}) + target_display_names = metadata.get("targetDisplayNames", []) + target_ids = metadata.get("targetIds", []) + + # Parse rules + rules = root.get("rules", {}) + + # Parse projects/incidents + projects = root.get("projects", []) + # Merge all projects into one component view + component_name = "" + component_display_name = "" + language = "N/A" + frameworks = "N/A" + build_tools = "N/A" + jdk_version = "" + all_incidents = [] + + for proj in projects: + props = proj.get("properties", {}) + if not component_name: + component_name = props.get("repo", "") or props.get("appName", "Application") + component_display_name = props.get("appName", "") or component_name + lang = props.get("languages") + if lang: + language = ", ".join(lang) if isinstance(lang, list) else str(lang) + fw = props.get("frameworks") + if fw: + frameworks = ", ".join(fw) if isinstance(fw, list) else str(fw) + tools = props.get("tools") + if tools: + build_tools = ", ".join(tools) if isinstance(tools, list) else str(tools) + if not jdk_version: + jdk_version = props.get("jdkVersion", "") + + for incident in proj.get("incidents", []): + rule_id = incident.get("ruleId", "") + if rule_id and rule_id in rules: + all_incidents.append(incident) + + # Parse security findings + security_findings = root.get("security", []) + # Parse rearchitect findings + rearchitect_findings = root.get("rearchitect", []) + + is_dotnet = is_dotnet_component(language) + + # Group incidents by rule + incidents_by_rule = {} + for inc in all_incidents: + rid = inc.get("ruleId", "") + if rid not in incidents_by_rule: + incidents_by_rule[rid] = [] + incidents_by_rule[rid].append(inc) + + # Classify into cloud/upgrade domains + cloud_groups = [] # list of (rule_id, incidents_list) + upgrade_groups = [] + upgrade_domain_name = "Upgrade" + + for rule_id, incs in incidents_by_rule.items(): + rule_obj = rules.get(rule_id, {}) + labels = rule_obj.get("labels", []) + has_domain = any(l.startswith("domain=") for l in labels) + is_cloud = any(l == "domain=cloud-readiness" for l in labels) + is_upgrade = any(l.endswith("-upgrade") for l in labels) + + if not has_domain and is_dotnet: + is_cloud = True + + if is_cloud: + cloud_groups.append((rule_id, incs)) + if is_upgrade: + upgrade_groups.append((rule_id, incs)) + upgrade_label = next((l for l in labels if l.endswith("-upgrade") and l.startswith("domain=")), None) + if upgrade_label: + domain_val = upgrade_label[len("domain="):] + upgrade_domain_name = " ".join(w.capitalize() for w in domain_val.split("-")) + + # Count criticalities per domain + def count_crits(groups): + m, p, o = 0, 0, 0 + for rid, _ in groups: + sev = rules.get(rid, {}).get("severity", "").strip().lower() + if sev == "mandatory": m += 1 + elif sev == "potential": p += 1 + else: o += 1 + return m, p, o + + cloud_crit = count_crits(cloud_groups) + upgrade_crit = count_crits(upgrade_groups) + + # Add rearchitect to upgrade optional count + if rearchitect_findings: + upgrade_crit = (upgrade_crit[0], upgrade_crit[1], upgrade_crit[2] + len(rearchitect_findings)) + + sec_mandatory = sum(1 for f in security_findings if f.get("severity", "").strip().lower() == "mandatory") + sec_potential = sum(1 for f in security_findings if f.get("severity", "").strip().lower() == "potential") + sec_optional = len(security_findings) - sec_mandatory - sec_potential + security_crit = (sec_mandatory, sec_potential, sec_optional) + + # Compute total effort + # For each unique rule, take aksEffort from first incident + total_effort = 0.0 + seen_rules = set() + for inc in all_incidents: + rid = inc.get("ruleId", "") + if rid in seen_rules: + continue + seen_rules.add(rid) + # Get AKS effort + aks_effort = 0 + targets = inc.get("targets", {}) + for tid, tdata in targets.items(): + if "aks" in tid.lower() or "kubernetes" in tid.lower(): + aks_effort = tdata.get("effort", 0) + break + if aks_effort == 0: + aks_effort = rules.get(rid, {}).get("effort", 0) or 0 + total_effort += aks_effort + + total_effort += sum(f.get("storyPoint", 0) for f in security_findings) + total_effort += len(rearchitect_findings) * 10 + + if total_effort < 20: + effort_label = "S" + elif total_effort < 50: + effort_label = "M" + elif total_effort < 100: + effort_label = "L" + else: + effort_label = "XL" + effort_display = f"{effort_label} (total story points: {int(total_effort)})" + + # Load fact tabs + facts_dir = os.path.join(report_dir, "facts") + # Fallback: if facts/ doesn't have the files, try engines/ directory + engines_dir = os.path.join(os.path.dirname(os.path.dirname(report_dir)), "engines") + fact_tab_data = [] # (tab_id, label, tooltip, html_content) + for tab_id, label, tooltip, filename in FACT_TABS: + fpath = os.path.join(facts_dir, filename) + if not os.path.isfile(fpath): + # Try engines/ as fallback + fpath = os.path.join(engines_dir, filename) + if os.path.isfile(fpath): + with open(fpath, 'r', encoding='utf-8') as f: + md_content = f.read() + html_content = markdown_to_fact_html(md_content) + fact_tab_data.append((tab_id, label, tooltip, html_content)) + + # ── Build HTML ─────────────────────────────────────────────────────────── + html = [] + html.append('<!DOCTYPE html>') + html.append('<html><head><meta charset="utf-8" />') + html.append('<meta name="viewport" content="width=device-width, initial-scale=1" />') + html.append(f'<title>Assessment - {escape(component_display_name or component_name)}') + html.append('') + html.append(MERMAID_HEAD_SCRIPT) + html.append('') + + # Lightbox + html.append('') + + html.append('
') + html.append(f'

{escape(component_display_name or component_name)}

') + + # App info card + html.append('
') + html.append('

Application Information

') + html.append('
') + html.append('
') + html.append('
') + html.append(f'
Application Name{escape(component_display_name or component_name)}
') + if jdk_version: + html.append(f'
Java Version{escape(jdk_version)}
') + html.append(f'
Effort{escape(effort_display)}
') + html.append('
') + html.append('
') + html.append(f'
Build Tools{escape(build_tools)}
') + html.append(f'
Frameworks{escape(frameworks)}
') + html.append('
') + html.append('
') + html.append('
') + html.append('
') + + # Tab navigation + html.append('') + + # Issues tab panel + html.append('
') + + # Target dropdown + if target_display_names and target_ids: + html.append('
') + html.append(' ') + html.append(' ') + html.append('
') + + # Issue Summary card + html.append('
') + html.append('

Issue Summary

') + html.append('
') + html.append('
') + + def render_domain_donut(title, m, p, o): + total = m + p + o + html.append('
') + html.append(build_donut_svg(m, p, o)) + html.append(f'

{escape(title)}

') + html.append(f'
{total} issue{"s" if total != 1 else ""}
') + html.append('
') + + if cloud_groups: + render_domain_donut("Cloud Readiness", *cloud_crit) + if upgrade_groups or rearchitect_findings: + render_domain_donut(upgrade_domain_name, *upgrade_crit) + if security_findings: + render_domain_donut("Security", *security_crit) + + html.append('
') + html.append('
') + html.append('
Mandatory
') + html.append('
Potential
') + html.append('
Optional
') + html.append('
') + html.append('
') + html.append('
') + + # Filter bar + available_domains = [] + if cloud_groups: + available_domains.append(("cloud-readiness", "Cloud Readiness")) + if upgrade_groups: + available_domains.append(("java-upgrade", upgrade_domain_name)) + if security_findings: + available_domains.append(("security", "Security")) + + available_crits = set() + for rid, _ in cloud_groups + upgrade_groups: + sev = rules.get(rid, {}).get("severity", "optional").strip().lower() + available_crits.add(sev) + for f in security_findings: + available_crits.add(f.get("severity", "optional").strip().lower() or "optional") + + html.append('
') + if len(available_domains) > 1: + html.append('
') + html.append(' ') + html.append('
') + for did, dlabel in available_domains: + html.append(f' ') + html.append('
') + html.append('
') + if len(available_crits) > 1: + html.append('
') + html.append(' ') + html.append('
') + for c in ["mandatory", "potential", "optional"]: + if c in available_crits: + html.append(f' ') + html.append('
') + html.append('
') + html.append(' Clear all') + html.append('
') + + # Issue sections + def render_issue_section(section_title, groups, domain_id, badge_html=None, extra_rearchitect=None): + if not groups and not extra_rearchitect: + return + html.append(f'
') + badge = badge_html or "" + html.append(f'

{escape(section_title)}{badge}

') + html.append(' ') + html.append(' ') + html.append(' ') + html.append(' ') + + # Sort by severity rank then count desc + ordered = sorted(groups, key=lambda g: (get_severity_rank(rules.get(g[0], {}).get("severity", "")), -len(g[1]))) + + section_no_spaces = section_title.replace(" ", "").lower() + for gi, (rule_id, incs) in enumerate(ordered): + rule_obj = rules.get(rule_id, {}) + title = rule_obj.get("title", rule_id) + severity = rule_obj.get("severity", "").strip().lower() + first_inc = incs[0] + + # Get AKS effort from first incident + story_point = 0 + targets_data = first_inc.get("targets", {}) + for tid, tdata in targets_data.items(): + if "aks" in tid.lower() or "kubernetes" in tid.lower(): + story_point = tdata.get("effort", 0) + break + if story_point == 0: + story_point = rule_obj.get("effort", 0) or 0 + + row_id = f"row-{section_no_spaces}-{gi}" + + if severity == "mandatory": + crit_text, crit_css = "Mandatory", "crit-square-mandatory" + elif severity == "potential": + crit_text, crit_css = "Potential", "crit-square-potential" + else: + crit_text, crit_css = "Optional", "crit-square-optional" + + # Build targets JSON + targets_json_obj = {} + for tid, tdata in targets_data.items(): + targets_json_obj[tid] = {"effort": tdata.get("effort", 0), "severity": tdata.get("severity")} + targets_json_str = json.dumps(targets_json_obj, separators=(',', ':')) + + html.append(f" ") + html.append(f' ') + html.append(f' ') + html.append(f' ') + html.append(' ') + + # Detail row + incidents_with_loc = [inc for inc in incs if inc.get("location")] + html.append(f' ') + html.append(' ') + html.append(' ') + + # Rearchitect findings + if extra_rearchitect: + for ri, finding in enumerate(extra_rearchitect): + row_id = f"row-{section_no_spaces}-rearch-{ri}" + old_val = finding.get("old", "") + explanation = finding.get("explanation", "") + detected_in = finding.get("detectedIn", {}) + all_files = (detected_in.get("configFiles") or []) + (detected_in.get("sourceFiles") or []) + + html.append(f' ') + html.append(f' ') + html.append(f' ') + html.append(f' ') + html.append(' ') + html.append(f' ') + html.append(' ') + html.append(' ') + + html.append(' ') + html.append('
Issue CategoryCriticalityStory Point
{escape(title)}
{crit_text}{story_point}
Framework obsoletion ({escape(old_val)})
Optional10
') + html.append('
') + + render_issue_section("Cloud Readiness", cloud_groups, "cloud-readiness") + render_issue_section(upgrade_domain_name, upgrade_groups, "java-upgrade", extra_rearchitect=rearchitect_findings) + + # Security section + if security_findings: + html.append('
') + html.append('

Security Experimental

') + html.append(' ') + html.append(' ') + html.append(' ') + html.append(' ') + + ordered_sec = sorted(security_findings, key=lambda f: (get_severity_rank(f.get("severity", "")), -f.get("storyPoint", 0))) + for fi, finding in enumerate(ordered_sec): + row_id = f"row-security-{fi}" + sev = finding.get("severity", "").strip().lower() + if sev == "mandatory": + crit_text, crit_css = "Mandatory", "crit-square-mandatory" + elif sev == "potential": + crit_text, crit_css = "Potential", "crit-square-potential" + else: + crit_text, crit_css = "Optional", "crit-square-optional" + + fid = finding.get("id", "") + ftitle = finding.get("title", "") + display_name = f"{fid}: {ftitle}" if ftitle else fid + sp = finding.get("storyPoint", 0) + + html.append(f' ') + html.append(f' ') + html.append(f' ') + html.append(f' ') + html.append(' ') + + html.append(f' ') + html.append(' ') + html.append(' ') + + html.append(' ') + html.append('
Issue CategoryCriticalityStory Point
{escape(display_name)}
{crit_text}{sp}
') + html.append('
') + + html.append('
') + + # Fact tab panels + for tab_id, _, _, content in fact_tab_data: + html.append(f'
') + html.append(f'
{content}
') + html.append('
') + + # Footer + html.append('') + + # Script + html.append('') + + # Initial target change + if target_ids: + html.append(f"") + + html.append('
') + + return '\n'.join(html) + + +def generate_from_js_markdown(report_dir): + """Generate report.html from js-assessment-report.md (JS/TS).""" + # Look for js-assessment-report.md in report_dir or parent directories + md_path = None + search_dir = report_dir + for _ in range(5): + candidate = os.path.join(search_dir, "js-assessment-report.md") + if os.path.isfile(candidate): + md_path = candidate + break + search_dir = os.path.dirname(search_dir) + + if not md_path: + print("Error: js-assessment-report.md not found", file=sys.stderr) + sys.exit(1) + + with open(md_path, 'r', encoding='utf-8') as f: + md_content = f.read() + + # Extract title from first heading + title_match = re.match(r'^#\s+(.+)', md_content) + title = title_match.group(1) if title_match else "Assessment Report" + + fact_html = markdown_to_fact_html(md_content) + + html = [] + html.append('') + html.append('') + html.append('') + html.append(f'Assessment - {escape(title)}') + html.append('') + html.append(MERMAID_HEAD_SCRIPT) + html.append('') + html.append('') + html.append('
') + html.append(f'

{escape(title)}

') + html.append(f'
{fact_html}
') + html.append('') + html.append('') + html.append('
') + + return '\n'.join(html) + + +def main(): + if len(sys.argv) < 2: + print("Usage: python generate_report_html.py /path/to/report-directory", file=sys.stderr) + sys.exit(1) + + report_dir = sys.argv[1] + if not os.path.isdir(report_dir): + print(f"Error: Directory not found: {report_dir}", file=sys.stderr) + sys.exit(1) + + report_json_path = os.path.join(report_dir, "report.json") + + if os.path.isfile(report_json_path): + output = generate_from_report_json(report_dir) + else: + # Look for JS/TS markdown + output = generate_from_js_markdown(report_dir) + + output_path = os.path.join(report_dir, "report.html") + with open(output_path, 'w', encoding='utf-8') as f: + f.write(output) + + print(f"Generated: {output_path}") + + +if __name__ == "__main__": + main() diff --git a/.github/skills/generate-report-md/SKILL.md b/.github/skills/generate-report-md/SKILL.md new file mode 100644 index 000000000..a761d3951 --- /dev/null +++ b/.github/skills/generate-report-md/SKILL.md @@ -0,0 +1,54 @@ +--- +name: generate-report-md +description: Generate report.md from assessment report data by running the generate-report-md.py script +--- + +# Generate Report Markdown + +Generate a `report.md` file from the assessment output by running the included Python script. + +## Execution + +Run the following command: + +```bash +python .github/modernize/assessment/engines/generate-report-md.py .github/modernize/assessment/reports/report-{reportId} +``` + +If the script is not at that path, look for it in the skill's own directory or copy it from there. The script file is `generate-report-md.py` bundled alongside this skill. + +Alternatively, if the script is available in the current working directory or skill resources: + +```bash +python generate-report-md.py .github/modernize/assessment/reports/report-{reportId} +``` + +## What the script does + +1. Reads `report.json` (Java/.NET) or `js-assessment-report.md` (JS/TS) from the report directory +2. Checks for available fact files in the `facts/` subdirectory +3. Generates `report.md` in the same directory + +## Prerequisites + +- Python 3 must be available in the environment +- The versioned report directory must already exist with `report.json` or the JS/TS report file +- Fact files should already be copied to the `facts/` subdirectory (if architecture analysis was enabled) + +## Input + +The script takes one argument: the path to the versioned report directory, e.g.: +`.github/modernize/assessment/reports/report-20240615143052` + +## Output + +`report.md` is written to the same directory as the input report. + +## Fallback + +If Python is not available or the script fails, you may generate report.md manually following the same structure as the local CLI renderer produces. The key requirements are: +- Summary table with Total Issues (count of unique rule groups, NOT file occurrences) +- Application Information table (without Story points row) +- Issue sections with anchor IDs derived from titles (not rule IDs) +- Codebase Insights section linking to available fact files +- No "Back to aggregate report" link diff --git a/.github/skills/generate-report-md/generate-report-md.py b/.github/skills/generate-report-md/generate-report-md.py new file mode 100644 index 000000000..2c01c3ed0 --- /dev/null +++ b/.github/skills/generate-report-md/generate-report-md.py @@ -0,0 +1,579 @@ +#!/usr/bin/env python3 +""" +Generates report.md from report.json (Java/.NET) or js-assessment-report.md (JS/TS). + +Usage: + python generate_report_md.py /path/to/.github/modernize/assessment/reports/report-{id} +""" + +import json +import os +import re +import sys + + +def make_anchor_id(title: str) -> str: + """Convert title to anchor ID: replace non-alnum/non-dash with _, collapse, trim trailing.""" + if not title: + return "issue" + result = [] + last_was_underscore = False + for ch in title: + if ch.isalnum() or ch == '-': + result.append(ch) + last_was_underscore = False + else: + if not last_was_underscore and result: + result.append('_') + last_was_underscore = True + if result and result[-1] == '_': + result.pop() + return ''.join(result) + + +def escape_table_cell(value: str) -> str: + if not value: + return "" + return value.replace("|", "\\|").replace("\r", "").replace("\n", " ") + + +def escape_html(value: str) -> str: + if not value: + return "" + return value.replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """) + + +def get_array_as_string(node) -> str: + """Convert a JSON node (array or string) to comma-separated string.""" + if node is None: + return "" + if isinstance(node, list): + values = [v for v in node if v and str(v).strip()] + return ", ".join(str(v) for v in values) if values else "" + if isinstance(node, str): + return node if node.strip() else "" + return str(node) + + +def get_severity_rank(severity: str) -> int: + s = (severity or "").strip().lower() + return {"mandatory": 0, "potential": 1, "optional": 2, "information": 3}.get(s, 999) + + +def is_dotnet_component(language: str) -> bool: + if not language or language.strip().lower() == "n/a": + return True + lang_lower = language.lower() + return ".net" in lang_lower or "c#" in lang_lower + + +def render_appcat_report(report_dir: str, report_json_path: str) -> str: + """Render report.md from report.json for Java/.NET.""" + with open(report_json_path, 'r', encoding='utf-8') as f: + root = json.load(f) + + rules = root.get("rules", {}) + projects = root.get("projects", []) + security_findings = root.get("security", []) + rearchitect_findings = root.get("rearchitect", []) + + if not projects: + return "" + + # Parse first component + project = projects[0] + properties = project.get("properties", {}) or {} + + component_name = properties.get("repo") or properties.get("appName") or "Others" + language = get_array_as_string(properties.get("languages")) or "N/A" + frameworks = get_array_as_string(properties.get("frameworks")) or "N/A" + build_tools = get_array_as_string(properties.get("tools")) or "N/A" + + # Merge all projects for same component + all_incidents = [] + for proj in projects: + incidents_array = proj.get("incidents", []) + for inc in incidents_array: + all_incidents.append(inc) + + # Group incidents by ruleId, track unique rules + rule_groups = {} # ruleId -> list of incidents + for inc in all_incidents: + rule_id = inc.get("ruleId", "") + if not rule_id or rule_id not in rules: + continue + rule_groups.setdefault(rule_id, []).append(inc) + + # Compute severity counts (unique rules) + mandatory_rules = set() + potential_rules = set() + optional_rules = set() + for rule_id in rule_groups: + rule_obj = rules.get(rule_id, {}) + severity = (rule_obj.get("severity") or "").strip().lower() + if severity == "mandatory": + mandatory_rules.add(rule_id) + elif severity == "potential": + potential_rules.add(rule_id) + elif severity == "optional": + optional_rules.add(rule_id) + + # Security finding counts + sec_mandatory = sum(1 for f in security_findings if (f.get("severity") or "").strip().lower() == "mandatory") + sec_potential = sum(1 for f in security_findings if (f.get("severity") or "").strip().lower() == "potential") + + total_issues = len(rule_groups) + len(security_findings) + mandatory_blockers = len(mandatory_rules) + sec_mandatory + potential_issues = len(potential_rules) + sec_potential + + # Classify rules into cloud/upgrade + is_dotnet = is_dotnet_component(language) + cloud_rules = [] + upgrade_rules = [] + + for rule_id, incidents in rule_groups.items(): + rule_obj = rules.get(rule_id, {}) + labels = rule_obj.get("labels", []) or [] + labels = [l for l in labels if l and str(l).strip()] + + has_domain_label = any(str(l).lower().startswith("domain=") for l in labels) + is_cloud = any(str(l).lower() == "domain=cloud-readiness" for l in labels) + is_upgrade = any(str(l).lower().endswith("-upgrade") for l in labels) + + if not has_domain_label and is_dotnet: + is_cloud = True + + if is_cloud: + cloud_rules.append(rule_id) + if is_upgrade: + upgrade_rules.append(rule_id) + + # Get AKS effort for each rule (first incident's target or rule-level) + def get_aks_effort(rule_id: str) -> float: + incidents = rule_groups.get(rule_id, []) + for inc in incidents: + targets = inc.get("targets", {}) or {} + for target_id, target_data in targets.items(): + if isinstance(target_data, dict) and "aks" in target_id.lower(): + effort = target_data.get("effort", 0) + if effort: + return float(effort) + break + # Fallback to rule-level + rule_obj = rules.get(rule_id, {}) + return float(rule_obj.get("effort", 0) or 0) + + def sort_key(rule_id): + rule_obj = rules.get(rule_id, {}) + severity = (rule_obj.get("severity") or "").strip().lower() + rank = get_severity_rank(severity) + count = len(rule_groups.get(rule_id, [])) + return (rank, -count) + + cloud_rules.sort(key=sort_key) + upgrade_rules.sort(key=sort_key) + + # Build markdown + lines = [] + lines.append(f"# {component_name}") + lines.append("") + lines.append("## Summary") + lines.append("") + lines.append("| Metric | Value |") + lines.append("|--------|-------|") + lines.append(f"| Total Issues | {total_issues} |") + lines.append(f"| Mandatory Blockers | {mandatory_blockers} |") + lines.append(f"| Potential Issues | {potential_issues} |") + lines.append("") + lines.append("## Application Information") + lines.append("") + lines.append("| Property | Value |") + lines.append("|----------|-------|") + lines.append(f"| Language | {escape_table_cell(language)} |") + lines.append(f"| Frameworks | {escape_table_cell(frameworks)} |") + lines.append(f"| Build tools | {escape_table_cell(build_tools)} |") + + # Conditional rows + jdk_version = properties.get("jdkVersion", "") + if jdk_version and str(jdk_version).strip(): + lines.append(f"| JDK version | {escape_table_cell(str(jdk_version))} |") + + description = properties.get("description", "") + if description and str(description).strip(): + lines.append(f"| Description | {escape_table_cell(str(description))} |") + + app_type = properties.get("applicationType", "") + if app_type and str(app_type).strip(): + lines.append(f"| Application type | {escape_table_cell(str(app_type))} |") + + tags = get_array_as_string(properties.get("tags")) + if tags: + lines.append(f"| Tags | {escape_table_cell(tags)} |") + + loc = properties.get("linesOfCode", "") + if loc and str(loc).strip(): + lines.append(f"| Lines of code | {escape_table_cell(str(loc))} |") + + # Do NOT include Story points + lines.append("") + + def render_issue_section(section_title: str, rule_ids: list): + if not rule_ids: + return + lines.append(f"## {section_title}") + lines.append("") + lines.append("| Issue Name | Criticality | Story Points | Occurrences |") + lines.append("|------------|-------------|--------------|-------------|") + + for rule_id in rule_ids: + rule_obj = rules.get(rule_id, {}) + title = rule_obj.get("title") or rule_id + severity = (rule_obj.get("severity") or "").strip().lower() + crit_label = {"mandatory": "Mandatory", "potential": "Potential"}.get(severity, "Optional") + effort = get_aks_effort(rule_id) + # Format effort: integer if whole, else float + effort_str = str(int(effort)) if effort == int(effort) else f"{effort:g}" + count = len(rule_groups.get(rule_id, [])) + anchor_id = make_anchor_id(title) + + # Check if any incident has a file path + has_locations = any( + (inc.get("location") or "").strip() + for inc in rule_groups.get(rule_id, []) + ) + occurrences_cell = f"[{count}](#{anchor_id})" if has_locations else str(count) + lines.append(f"| {escape_table_cell(title)} | {crit_label} | {effort_str} | {occurrences_cell} |") + + lines.append("") + lines.append("### Issue Details") + lines.append("") + + for rule_id in rule_ids: + rule_obj = rules.get(rule_id, {}) + title = rule_obj.get("title") or rule_id + incidents = rule_groups.get(rule_id, []) + + locations = [] + for inc in incidents: + file_path = (inc.get("location") or "").strip() + if not file_path: + continue + line_num = inc.get("line") + if line_num is not None: + try: + line_num = int(line_num) + locations.append(f"{file_path} (line {line_num})") + except (ValueError, TypeError): + locations.append(file_path) + else: + locations.append(file_path) + + if not locations: + continue + + anchor_id = make_anchor_id(title) + lines.append(f'
') + lines.append(f"{escape_html(title)} — affected files") + lines.append("") + for loc in locations: + lines.append(f"- `{loc}`") + lines.append("") + lines.append("
") + lines.append("") + + render_issue_section("Cloud Readiness Issues", cloud_rules) + render_issue_section("Upgrade Issues", upgrade_rules) + + # Rearchitect findings + if rearchitect_findings: + lines.append("## Rearchitect Findings") + lines.append("") + lines.append("> **Note:** These findings were generated by AI and may contain inaccuracies or incomplete information. Please review carefully.") + lines.append("") + lines.append("| Finding | Old | New | Story Points | Files |") + lines.append("|---------|-----|-----|--------------|-------|") + + for finding in rearchitect_findings: + name = finding.get("name", "") + old = finding.get("old", "") + new = finding.get("new", "") + story_points = finding.get("storyPoints", 5) + detected_in = finding.get("detectedIn", {}) or {} + config_files = detected_in.get("configFiles", []) or [] + source_files = detected_in.get("sourceFiles", []) or [] + all_files = config_files + source_files + anchor_id = make_anchor_id(name) + files_cell = f"[{len(all_files)}](#{anchor_id})" if all_files else "0" + lines.append(f"| {escape_table_cell(name)} | {escape_table_cell(old)} | {escape_table_cell(new)} | {story_points} | {files_cell} |") + + lines.append("") + lines.append("### Rearchitect Finding Details") + lines.append("") + + for finding in rearchitect_findings: + name = finding.get("name", "") + detected_in = finding.get("detectedIn", {}) or {} + config_files = detected_in.get("configFiles", []) or [] + source_files = detected_in.get("sourceFiles", []) or [] + all_files = config_files + source_files + if not all_files: + continue + anchor_id = make_anchor_id(name) + lines.append(f'
') + lines.append(f"{escape_html(name)} — affected files") + lines.append("") + for file in all_files: + lines.append(f"- `{file}`") + lines.append("") + explanation = finding.get("explanation", "") + if explanation and explanation.strip(): + lines.append(f"**Explanation:** {explanation}") + lines.append("") + lines.append("
") + lines.append("") + + # Security Issues + if security_findings: + lines.append("## Security Issues") + lines.append("") + lines.append("> **Note:** These issues were generated by AI and may contain inaccuracies or incomplete information. Please review carefully.") + lines.append("") + lines.append("| Issue Name | Criticality | Story Points | Files |") + lines.append("|------------|-------------|--------------|-------|") + + ordered_findings = sorted( + security_findings, + key=lambda f: (get_severity_rank((f.get("severity") or "").strip().lower()), -(f.get("storyPoint", 0) or 0)) + ) + + for finding in ordered_findings: + severity = (finding.get("severity") or "").strip().lower() + crit_label = {"mandatory": "Mandatory", "potential": "Potential"}.get(severity, "Optional") + finding_id = finding.get("id", "") + title = finding.get("title", "") + display_name = f"{finding_id}: {title}" if title and title.strip() else finding_id + story_point = finding.get("storyPoint", 0) or 0 + evidence = finding.get("evidence", {}) or {} + files = evidence.get("files", []) or [] + anchor_id = make_anchor_id(display_name) + files_cell = f"[{len(files)}](#{anchor_id})" if files else "0" + lines.append(f"| {escape_table_cell(display_name)} | {crit_label} | {story_point} | {files_cell} |") + + lines.append("") + lines.append("### Security Issue Details") + lines.append("") + + for finding in ordered_findings: + evidence = finding.get("evidence", {}) or {} + files = evidence.get("files", []) or [] + if not files: + continue + finding_id = finding.get("id", "") + title = finding.get("title", "") + display_name = f"{finding_id}: {title}" if title and title.strip() else finding_id + anchor_id = make_anchor_id(display_name) + lines.append(f'
') + lines.append(f"{escape_html(display_name)} — affected files") + lines.append("") + for file in files: + lines.append(f"- `{file}`") + lines.append("") + lines.append("
") + lines.append("") + + lines.append("---") + lines.append("") + + # Codebase Insights + append_codebase_insights(lines, report_dir) + + lines.append("[Share feedback](https://aka.ms/ghcp-appmod/feedback)") + + return '\n'.join(lines) + '\n' + + +def append_codebase_insights(lines: list, report_dir: str): + """Append Codebase Insights section with links to available fact files.""" + fact_docs = [ + ("architecture-diagram.md", "Architecture Diagram", "Understand the big picture: system layers and component relationships"), + ("dependency-map.md", "Dependency Map", "Know what the project depends on and where the risks are"), + ("api-service-contracts.md", "API & Service Contracts", "See how services communicate and what contracts they expose"), + ("data-architecture.md", "Data Architecture", "Explore data models, storage, and data flow patterns"), + ("configuration-inventory.md", "Configuration Inventory", "Review how the application is configured across environments"), + ("business-workflows.md", "Business Workflows", "Trace end-to-end business processes and domain logic"), + ] + + facts_dir = os.path.join(report_dir, "facts") + + lines.append("## Codebase Insights") + lines.append("") + lines.append("> **Note:** These documents are generated by AI and may contain inaccuracies or incomplete information. Please review carefully.") + lines.append("") + + existing = [] + if os.path.isdir(facts_dir): + for filename, title, desc in fact_docs: + if os.path.isfile(os.path.join(facts_dir, filename)): + existing.append((filename, title, desc)) + + if not existing: + lines.append("> **Codebase Insights aren't available yet.**") + lines.append(">") + lines.append("> These documents are generated when assessment runs with **Full analysis** coverage. Re-run the assessment and set `analysisCoverage: full` to enable them.") + lines.append("") + return + + for i, (filename, title, desc) in enumerate(existing, 1): + lines.append(f"{i}. **[{title}](facts/{filename})** — {desc}") + + lines.append("") + + +def render_jsts_report(report_dir: str, ncu_path: str) -> str: + """Render report.md from js-assessment-report.md for JS/TS.""" + with open(ncu_path, 'r', encoding='utf-8') as f: + ncu_output = f.read() + + # Parse ncu output + dependencies = [] + patch_count = 0 + minor_count = 0 + major_count = 0 + zero_major_count = 0 + package_manager = "npm" + current_category = "" + current_category_display = "" + + for line in ncu_output.split('\n'): + trimmed = line.strip() + if trimmed.startswith("Using "): + package_manager = trimmed[len("Using "):].strip() + elif trimmed.startswith("Patch"): + current_category = "patch" + current_category_display = "Patch" + elif trimmed.startswith("Minor"): + current_category = "minor" + current_category_display = "Minor" + elif trimmed.startswith("Major Potentially breaking"): + current_category = "major" + current_category_display = "Major" + elif trimmed.startswith("Major version zero"): + current_category = "zero-major" + current_category_display = "Major (0.x)" + elif trimmed and '→' in trimmed: + arrow_idx = trimmed.index('→') + before_arrow = trimmed[:arrow_idx].strip() + after_arrow = trimmed[arrow_idx + 1:].strip() + + parts = before_arrow.split() + name = ' '.join(parts[:-1]) if len(parts) >= 2 else before_arrow + current_version = parts[-1] if len(parts) >= 2 else "" + + dependencies.append((name, current_version, after_arrow, current_category_display)) + + if current_category == "patch": + patch_count += 1 + elif current_category == "minor": + minor_count += 1 + elif current_category == "major": + major_count += 1 + elif current_category == "zero-major": + zero_major_count += 1 + + total_count = patch_count + minor_count + major_count + zero_major_count + + # Derive repo name from directory name + repo_name = os.path.basename(report_dir) + + lines = [] + lines.append(f"# {repo_name}") + lines.append("") + lines.append("JavaScript/TypeScript Dependency Assessment") + lines.append("") + lines.append("## Application Information") + lines.append("") + lines.append("| Property | Value |") + lines.append("|----------|-------|") + lines.append("| Language | JavaScript/TypeScript |") + lines.append(f"| Build tools | {escape_table_cell(package_manager)} |") + lines.append("") + lines.append("## Summary") + lines.append("") + lines.append("| Update Type | Count |") + lines.append("|-------------|-------|") + lines.append(f"| **Total Updates** | **{total_count}** |") + lines.append(f"| Patch | {patch_count} |") + lines.append(f"| Minor | {minor_count} |") + lines.append(f"| Major | {major_count} |") + if zero_major_count > 0: + lines.append(f"| Major (0.x) | {zero_major_count} |") + lines.append("") + + if dependencies: + lines.append("## Dependency Updates") + lines.append("") + lines.append("| Package | Current | Target | Type |") + lines.append("|---------|---------|--------|------|") + for name, current, target, category in dependencies: + lines.append(f"| {escape_table_cell(name)} | {escape_table_cell(current)} | {escape_table_cell(target)} | {category} |") + lines.append("") + + lines.append("## Recommendations") + lines.append("") + lines.append("| Update Type | Guidance |") + lines.append("|-------------|---------|") + lines.append("| Patch & Minor | Generally safe to apply. Consider updating these first. |") + lines.append("| Major | Review breaking changes in package release notes before updating. |") + lines.append("| Major (0.x) | Exercise caution — these packages follow unstable version semantics. |") + lines.append("") + lines.append("---") + lines.append("") + + append_codebase_insights(lines, report_dir) + + lines.append("[Share feedback](https://aka.ms/ghcp-appmod/feedback)") + + return '\n'.join(lines) + '\n' + + +def main(): + if len(sys.argv) < 2: + print("Usage: python generate_report_md.py ", file=sys.stderr) + sys.exit(1) + + report_dir = sys.argv[1] + + if not os.path.isdir(report_dir): + print(f"Error: Directory not found: {report_dir}", file=sys.stderr) + sys.exit(1) + + report_json_path = os.path.join(report_dir, "report.json") + + if os.path.isfile(report_json_path): + # Java/.NET mode + markdown = render_appcat_report(report_dir, report_json_path) + else: + # JS/TS mode - look for js-assessment-report.md + ncu_path = os.path.join(report_dir, "js-assessment-report.md") + if not os.path.isfile(ncu_path): + # Check parent directory + parent = os.path.dirname(report_dir) + ncu_path = os.path.join(parent, "js-assessment-report.md") + if not os.path.isfile(ncu_path): + print("Error: Neither report.json nor js-assessment-report.md found.", file=sys.stderr) + sys.exit(1) + markdown = render_jsts_report(report_dir, ncu_path) + + if not markdown: + print("Error: Failed to generate report.", file=sys.stderr) + sys.exit(1) + + output_path = os.path.join(report_dir, "report.md") + with open(output_path, 'w', encoding='utf-8') as f: + f.write(markdown) + + print(f"Generated: {output_path}") + + +if __name__ == "__main__": + main() diff --git a/.github/skills/integration-tests/SKILL.md b/.github/skills/integration-tests/SKILL.md new file mode 100644 index 000000000..0a33aa89a --- /dev/null +++ b/.github/skills/integration-tests/SKILL.md @@ -0,0 +1,324 @@ +--- +name: integration-tests +description: Run multi-layer integration tests for modernized Java applications. Supports 4 layers - Layer 1 (TestContainers), Layer 2 (Smoke Tests), Layer 3 (Azure Integration), Layer 4 (Behavioral Comparison). **Java projects only** - skip if source code is not Java. +--- + +## Language Support + +**This skill supports Java projects only.** If the source code is not Java (e.g., .NET, Python, Node.js), skip test generation and report that integration tests are not supported for this language. + +## User Input +- **layer** (Optional): Which layer to test (1, 2, 3, or 4). Default: 1 +- **azure-config** (Optional, Layer 3 only): Azure environment configuration. If not provided, read from `./infra/infra-config.md`. +- **modernization-work-folder** (Optional): Directory path for generating plan and summary files. Default: `.github` +- **test-root** (Optional): The root directory for integration tests. Default: current working directory. All application modules found in the directory are included in integration tests. + +## Available references + +### Layer 1: Local Integration Tests +**Read [references/layer1-local-integration.md](references/layer1-local-integration.md) first**, then create TestContainers-based integration test classes. + +### Layer 2: Smoke Tests +**Read [references/layer2-smoke-tests.md](references/layer2-smoke-tests.md) first.** Layer 2 uses shell-based smoke tests with docker-compose, NOT JUnit test classes. Follow the exact multi-commit workflow (artifacts → auth → restore) documented in the reference file. + +### Layer 3: Azure Integration Tests +**Read [references/layer3-azure-integration.md](references/layer3-azure-integration.md) first**, then create integration test classes that connect to real Azure services. + +### Layer 4: Behavioral Comparison +**Read [references/layer4-behavioral-comparison.md](references/layer4-behavioral-comparison.md) first**, then create comparison tests that validate behavior matches between old and new implementations. + +### TestContainers Coding References +- **Azure Service Bus with TestContainers Coding Reference**, see [references/layer1-servicebus-integration.md](references/azure-servicebus-testcontainers.md) +- **Azure Storage with TestContainers Coding Reference**, see [references/layer1-blobstorage-integration.md](references/azure-storage-testcontainers.md) + +## Workflow + +1. Analyze the project to identify modules that need to be tested and any existing integration tests. If git history is available, analyze past commits to understand which components were modified during modernization and prioritize testing those areas. +2. Create an integration test plan file at `{modernization-work-folder}/integration-tests/integration-test-plan.md` that outlines: + - Testing strategy and approach for the detected app modules + - Testing strategy and approach for each layer + - Identified components requiring integration testing + - Dependencies and test setup requirements + - Expected test scenarios and validation criteria +3. Implement new integration tests following the principles outlined below +4. Execute the tests +5. If issues found: + - Analyze failures using the Test vs Source Code Decision Framework to determine whether to fix test code or source code + - Fix source code if the failure indicates a real problem in the application logic. + - Fix test code if the failure is due to unrealistic test scenarios, incorrect test setup. + - Execute tests again after fixes +6. **Only proceed when all tests run and pass**, or exit after 20 attempts +7. Create an integration test summary file at `{modernization-work-folder}/integration-tests/integration-test-summary.md` that documents: + - All integration tests added (with file paths and descriptions) + - Test coverage improvements achieved + - Final test execution results +8. Commit all code changes with brief and meaningful commit messages + +## Integration Tests Writing Principles + +**CRITICAL - Read Reference Docs First:** +- **Before starting ANY layer**, read the corresponding reference file in [references/](./references/) directory + +Analyze the project if integration tests have covered all components, if not **DO ADD** new integration tests by the following principles: + +- **Purpose:** Ensures combined components function as a whole, focusing on "in-between" logic rather than individual module functionality. +- **DO use** top-down approach to add integration tests. For example, if an application has controller layer, service layer, and database layer. The integration tests should set up real connections to the database, and then test against the controller layer, to validate all functionalities. +- **DO focus on:** Validates interactions between modules, databases, messaging services, and file systems. +- **DO NOT** change the technical stack, architure selection, libary using in the source code. The goal is to validate the existing application code, not change it to pass tests. +- **DO fix issues** in the appropriate place (test or source code) based on the analysis. +- **DO write** comprehensive integration covering **ALL** components. +- **DO test through the application's own classes.** Every test must instantiate and invoke the project's own classes/methods. Never call third-party SDK APIs directly as a substitute for testing application code. Tests that only exercise Azure/Redis/PostgreSQL SDK operations without going through the application's handler/service/manager classes will score very low on functional coverage. +- **DO use** the layer-specific class name suffix from the Test Isolation Convention (e.g., `L1Test` for Layer 1, `L3Test` for Layer 3, `L4Test` for Layer 4). **Layer 2 does not use test classes.** +- **DO annotate** every test class with the layer-specific tag/category from the Test Isolation Convention. +- **DO verify** that `mvn verify` (or equivalent build command) discovers and runs all tests without extra flags. If tests require `-Dtest=...` to be found, the build configuration is wrong. +- **DO NOT** leave commented-out tests or dead test code. Every `@Test` annotation must be on a working test method. +- **DO NOT** add extra modules for integration tests, write integration tests in the existing modules. +- **DO commit** changes separately for each layer with meaningful commit messages. Do not combine changes from different layers into a single commit. + - **Layer 1, 3, 4**: Single commit per layer (e.g., `Add Layer 1 local integration tests`). Generate runner scripts and include them in the same commit. + - **Layer 2**: Multi-commit sequence as defined in [layer2-smoke-tests.md](./references/layer2-smoke-tests.md) (artifacts → auth → restore). **CRITICAL: Layer 2 does NOT create test classes - it uses shell-based smoke tests with docker-compose.** Runner scripts are part of the artifacts commit. + + +### Test Isolation Convention + +When multiple layers coexist in the same project, tests must be distinguishable. **Every layer MUST use a distinct class name suffix AND tag/category** so tests from different layers never interfere with each other. + +#### Naming Convention + +| Layer | Class Name Suffix | Example Class Name | +|-------|-------------------|--------------------| +| 1 | `L1Test` | `BlobStorageL1Test`, `OrderServiceL1Test` | +| 2 | N/A - No test classes | Layer 2 uses shell-based smoke tests, not test classes. See [layer2-smoke-tests.md](./references/layer2-smoke-tests.md) | +| 3 | `L3Test` | `AzureSqlL3Test`, `BlobStorageL3Test` | +| 4 | `L4Test` | `OrderApiL4Test`, `UserServiceL4Test` | + +#### Tagging / Category Convention + +Test classes for Layers 1, 3, 4 **MUST** be annotated with a layer-specific tag so the runner script can filter precisely. **Layer 2 does not use test classes** (see [layer2-smoke-tests.md](./references/layer2-smoke-tests.md)). + +| Layer | JUnit 5 | JUnit 4 | +|-------|---------|---------| +| 1 | `@Tag("Layer1")` | `@Category(Layer1.class)` | +| 2 | N/A - Shell-based | N/A - Shell-based | +| 3 | `@Tag("Layer3")` | `@Category(Layer3.class)` | +| 4 | `@Tag("Layer4")` | `@Category(Layer4.class)` | + +> **Rule**: Never rely solely on class name patterns for filtering. Always use tags/categories as the primary filter mechanism. + +### NEVER Mock the Migrated Service + +**This is the most important rule in this entire skill — it applies to ALL layers.** + +Integration tests MUST use real TestContainers for layer 1 and 2 and real cloud services for Layer 3 for every migrated dependency. Do NOT mock or `@MockBean` the SDK client that the migrated code uses (e.g., `ServiceBusTemplate`, `ServiceBusSenderClient`, `BlobServiceClient`, `RedisTemplate`, `DataSource`). Instead, spin up a real container and wire the application's own service to it. + +#### The Anti-Pattern You MUST Avoid + +```java +// ❌ WRONG — mocks the migrated service and SDK clients, tests unchanged code instead +@SpringBootTest +@ActiveProfiles("test") +class MyAppL1Test { + @MockBean private MessageService messageService; // THE MIGRATED CLASS — never tested! + @MockBean private ServiceBusSenderClient senderClient; // should be from real emulator + @MockBean private TokenCredential tokenCredential; // should be from emulator config + @Autowired private UserRepository userRepo; // unchanged code + + @Test void testUserCrud() { + userRepo.save(new User("test")); // tests unchanged code, NOT the migration + } +} +``` + +```java +// ✅ RIGHT — uses real Service Bus emulator, ALL beans wired from containers, tests through migrated service +@SpringBootTest +@ActiveProfiles("test") +@Testcontainers +class MyAppL1Test { + @Container static final GenericContainer SERVICE_BUS = ...; // real emulator + // No @MockBean at all — all beans come from containers + @DynamicPropertySource + @Autowired private MessageService messageService; // THE MIGRATED CLASS — tested for real! + + @DynamicPropertySource + static void props(DynamicPropertyRegistry registry) { + registry.add("spring.cloud.azure.servicebus.connection-string", () -> getConnectionString()); + // ... other container-derived properties + } + + @Test void testSendMessage() { + messageService.sendMessage("queue", "Hello"); // exercises the actual migrated code + } +} +``` + +## Handling Test Failures + +When integration tests fail during execution, use this framework to determine whether to fix the test code or the source code: + +### Fix Source Code When: + +**Business Logic Violations** +- Error indicates source code violates business rules (e.g., negative inventory allowed) +- Multiple similar tests fail with same pattern +**Specification Compliance** +- Source code doesn't implement required functionality properly +- Error messages show missing or incorrect behavior +**Cross-Component Integration Issues** +- Test setup is correct and realistic +- Source code fails to properly communicate between modules +- Data transformation or mapping errors between layers +**Resource Management Problems** +- Test uses proper connection/resource patterns +- Source code has leaks, deadlocks, or improper disposal +- Timing issues in source code (not test race conditions) + +### Fix Test Code When: + +**Test Implementation Issues** +- Unrealistic test data or scenarios +- Incorrect test setup (wrong mocks, invalid configurations) +- Testing implementation details rather than behavior +- Race conditions or timing issues in test logic +**Environmental Problems** +- Wrong container configurations or versions +- Test dependencies not properly isolated +- Hard-coded values that should be configurable +- Test cleanup issues affecting subsequent tests +**Test Design Flaws** +- Tests making too many assumptions about internal state +- Over-mocking leading to false confidence +- Testing edge cases that don't reflect real usage +- Assertions on wrong data or wrong timing + +### Analysis Steps: + +1. **Review Test Quality**: Does the test follow established patterns and realistic scenarios? +2. **Check Business Logic**: Does the failure indicate business rule violations in source code? +3. **Verify Setup**: Are test dependencies and configurations realistic and correct? +4. **Assess Error Type**: Is it a logic error, integration error, or test infrastructure issue? +5. **Consider Impact**: Would fixing source code improve real application behavior? + +### Decision Process: + +``` +Test Failure + │ + ├─ Does test model realistic business scenario? + │ ├─ No → Fix Test Code + │ └─ Yes ↓ + │ + ├─ Does source code violate business rules? + │ ├─ Yes → Fix Source Code + │ └─ No ↓ + │ + ├─ Is test setup and environment correct? + │ ├─ No → Fix Test Code + │ └─ Yes ↓ + │ + └─ Does error show integration/logic problem? + ├─ Yes → Fix Source Code + └─ No → Fix Test Code +``` + +## Standardized Runner Scripts + +After all tests are written, executed, and fixed to pass, generate a fixed runner script so users can re-run integration tests with a single command regardless of project type. + +| Layer | Script Path | Command (Unix) | Command (Windows) | +|-------|-------------|----------------|--------------------|| +| 1 | `{modernization-work-folder}/integration-tests/run-layer1-tests.sh` / `.ps1` | `bash {modernization-work-folder}/integration-tests/run-layer1-tests.sh` | `powershell {modernization-work-folder}/integration-tests/run-layer1-tests.ps1` | +| 2 | `{modernization-work-folder}/integration-tests/run-layer2-tests.sh` / `.ps1` | `bash {modernization-work-folder}/integration-tests/run-layer2-tests.sh` | `powershell {modernization-work-folder}/integration-tests/run-layer2-tests.ps1` | +| 3 | `{modernization-work-folder}/integration-tests/run-layer3-tests.sh` / `.ps1` | `bash {modernization-work-folder}/integration-tests/run-layer3-tests.sh` | `powershell {modernization-work-folder}/integration-tests/run-layer3-tests.ps1` | +| 4 | `{modernization-work-folder}/integration-tests/run-layer4-tests.sh` / `.ps1` | `bash {modernization-work-folder}/integration-tests/run-layer4-tests.sh` | `powershell {modernization-work-folder}/integration-tests/run-layer4-tests.ps1` | + +### Runner Script Requirements + +1. **Always generate both `.sh` and `.ps1` variants** for cross-platform support. +2. The script **MUST** encapsulate all project-specific details (build tool, test runner, filters, working directory, container setup/teardown). +3. The script **MUST** be self-contained — users should not need to know the build system or test framework to run it. +4. The script **MUST** exit with code 0 on success and non-zero on failure. +5. The script **MUST** print a human-readable result summary at the end: + - **On success**: `✅ Layer 1 integration tests PASSED` + - **On failure**: `❌ Layer 1 integration tests FAILED` + - The test runner's own console output already includes detailed counts, failure logs, assertion messages, and stack traces — the script only needs a clear pass/fail signal at the end. +6. For Layer 1, the script **MUST** verify Docker is running before starting tests. +7. For Layer 2, the script **MUST** handle starting and stopping the application. +8. For Layer 3, the script **SHOULD** accept Azure configuration via environment variables. +9. For Layer 4, the script **MUST** handle starting both old and new application versions. + +### Runner Script Filtering + +**Layers 1, 3, 4** use tag/category filters to execute test classes. **Layer 2 uses shell commands** (see [layer2-runner-script-templates.md](./references/layer2-runner-script-templates.md)). + +| Layer | Maven | Gradle | +|-------|-------|--------| +| 1 | `mvn verify -Dgroups=Layer1` | `./gradlew test -Dgroups=Layer1` | +| 2 | Shell-based smoke tests | Shell-based smoke tests | +| 3 | `mvn verify -Dgroups=Layer3` | `./gradlew test -Dgroups=Layer3` | +| 4 | `mvn verify -Dgroups=Layer4` | `./gradlew test -Dgroups=Layer4` | + +### Example Runner Script Structure (Layer 1) + +```bash +#!/bin/bash +set -euo pipefail + +# --- Auto-generated integration test runner --- +# Project type: +# Generated on: + +# Verify prerequisites +if ! docker info > /dev/null 2>&1; then + echo "ERROR: Docker is not running. Please start Docker and try again." + exit 1 +fi + +# Run integration tests (project-specific command is embedded here) +# IMPORTANT: Always filter by the layer-specific tag to avoid running other layers' tests +# Test output (including any failure logs) goes directly to the console. +cd "$(dirname "$0")/../.." + + +# e.g., mvn verify -Dgroups=Layer1, ./gradlew test -Dgroups=Layer1 + +TEST_EXIT=$? + +# --- Print summary --- +echo "" +echo "========================================" +if [ $TEST_EXIT -eq 0 ]; then + echo "✅ Layer 1 integration tests PASSED" +else + echo "❌ Layer 1 integration tests FAILED" +fi +echo "========================================" +exit $TEST_EXIT +``` + +> **Note to implementers:** Replace `` with the real test command for the detected project type (Maven/Gradle). The test runner's own console output already includes detailed results — the script just appends a clear pass/fail signal at the end. + +## Completion Criteria + +1. **Integration Test Plan**: Create and output a plan file at `{modernization-work-folder}/integration-tests/integration-test-plan.md` that includes: + - Analysis of existing test coverage gaps + - Identified components requiring integration testing + - Testing strategy and approach for each component + - Dependencies and test setup requirements + - Expected test scenarios and validation criteria +2. All tests for the requested layer **run and pass**, show the running results to the user +3. Test results are reported with clear pass/fail status +4. Any failures are properly analyzed and resolved (see Handling Test Failures section) +5. Test artifacts (logs, screenshots, comparison reports) are saved + +6. **Version Control**: Commit changes separately for each layer with meaningful commit messages. Do not combine changes from different layers into a single commit. + - **Layer 1, 3, 4**: Single commit per layer including test classes and runner scripts (e.g., `Add Layer 1 local integration tests`) + - **Layer 2**: Multi-commit sequence as defined in [layer2-smoke-tests.md](./references/layer2-smoke-tests.md) (minimum 3 commits: artifacts → auth → restore). Runner scripts are part of the artifacts commit. + - **Git ignore respect**: Use standard `git add` commands. Do not force-add files. If files in `{modernization-work-folder}` are ignored by the project's `.gitignore`, respect that. + +7. **Integration Test Summary**: Create and output a summary file at `{modernization-work-folder}/integration-tests/integration-test-summary.md` that documents: + - All integration tests added (with file paths and descriptions) + - Test coverage improvements achieved + - Issues identified and resolved (both in source code and test code) + - Final test execution results + - Paths to generated runner scripts and the fixed commands to execute them + - Source code changes made during testing and their purpose +8. **Runner Scripts**: Generate standardized runner scripts at `{modernization-work-folder}/integration-tests/run-layer{N}-tests.sh` and `.ps1` (see Standardized Runner Scripts section). The scripts must embed all project-specific commands so users always run the same fixed command. Include runner scripts in the layer's commit (for Layer 2, in the artifacts commit). diff --git a/.github/skills/integration-tests/references/azure-auth-strategies.md b/.github/skills/integration-tests/references/azure-auth-strategies.md new file mode 100644 index 000000000..5300d4636 --- /dev/null +++ b/.github/skills/integration-tests/references/azure-auth-strategies.md @@ -0,0 +1,189 @@ +# Azure Authentication Strategies for Smoke Tests + +Migrated applications typically use **Managed Identity** (`DefaultAzureCredential`) which only works on Azure infrastructure. For local smoke testing, applications must connect to local emulators or have Azure services disabled. + +## Azure Dependency Classification + +Classify each Azure dependency into one of three categories: + +| Category | Smoke Test Strategy | Example Services | +|----------|---------------------|------------------| +| **Emulatable** | Use local emulator with connection string auth | Azurite, Cosmos Emulator, Service Bus Emulator, Event Hubs Emulator | +| **Lazy** | Skip — not validated at startup | On-demand blob uploads, external REST APIs | +| **Startup-required, non-emulatable** | Disable via config, or recommend Layer 3 | Key Vault, App Configuration | + +### Emulatable Azure Services + +These services have official emulators that can run in Docker containers: + +| Azure Service | Emulator Image | Dependencies | +|---------------|----------------|--------------| +| Blob / Queue / Table Storage | `mcr.microsoft.com/azure-storage/azurite` | None | +| Cosmos DB | `mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator` | None (but requires SSL trust store + endpoint/key auth) | +| SQL Database | `mcr.microsoft.com/mssql/server` | None (wire-compatible with SQL Server) | +| Service Bus | `mcr.microsoft.com/azure-messaging/servicebus-emulator` | Requires MSSQL companion + JSON config | +| Event Hubs | `mcr.microsoft.com/azure-messaging/eventhubs-emulator` | Requires Azurite companion + JSON config | + +> **Note:** Service Bus and Event Hubs emulators require companion containers and a JSON config file. See [azure-servicebus-testcontainers.md](./azure-servicebus-testcontainers.md) for the config format. + +**Cosmos DB emulator note:** Unlike other emulators which use connection strings, the Cosmos DB emulator authenticates via **endpoint + account key** and requires an **SSL certificate** imported into a trust store (e.g., `javax.net.ssl.trustStore` for Java). If SSL setup is too complex, treat Cosmos DB as non-emulatable and recommend Layer 3. + +### Lazy Azure Connections + +If an Azure client is only used inside request handlers or scheduled jobs (no `@PostConstruct` / `IHostedService.StartAsync` / health check probing it), it's lazy. **Skip it** — the app starts fine without it. + +**How to identify:** +- Client is injected but never called during startup +- Client is used only in REST endpoint handlers +- Client is used in background jobs that start after initialization + +### Startup-Required, Non-Emulatable Services + +Some Azure services are required at startup but have no local emulator. + +**Resolution strategies (in priority order):** + +1. **Disable via config** — e.g., `spring.cloud.azure.keyvault.secret.enabled=false` or `KeyVault__Enabled=false` with an `if` guard around registration. +2. **Stub the health check** — Skip that health indicator in the smoke profile. +3. **Accept partial startup** — Non-fatal errors are OK for Layer 2 if the app reaches a healthy state. +4. **Skip Layer 2, recommend Layer 3** — If the app cannot start without a real Azure connection. + +## Authentication Modification Strategies + +For each emulatable Azure dependency, modify how the application authenticates: + +### Config-Driven Applications + +**Examples:** Spring Boot applications with `application.properties` or `application.yml`, Quarkus/Micronaut applications with `application.properties` + +**When to use:** Applications that use a configuration framework to externalize settings. These apps read connection strings from config files at runtime. + +**Strategy:** Modify config files to add explicit emulator connection strings. Create or modify the main config file (NOT environment variables, NOT test-only config files). + +**Which config file to modify:** +- Spring Boot: `src/main/resources/application.properties` or `src/main/resources/application.yml` +- Quarkus: `src/main/resources/application.properties` + +**CRITICAL:** You MUST always modify the config file to add the explicit connection string value. Do NOT rely on environment variables. + +**Example:** + +`src/main/resources/application.properties`: +```properties +azure.storage.blob.connection-string=DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://localhost:10000/devstoreaccount1; +spring.data.mongodb.uri=mongodb://localhost:27017/mydb +``` + +**If the application code uses `@Bean` methods or manual client construction:** You may also need to modify the code to read these config properties if it doesn't already. Add code to accept the connection string property with fallback to the original auth. Then add the connection string value to the config file as shown above. + +### Hardcoded/Plain Applications + +**Examples:** Plain Java CLI apps without config frameworks + +**When to use:** Applications that have NO configuration framework (no Spring Boot, no application.properties). These apps construct clients directly in code with hardcoded values. + +**Strategy:** Modify the source code to hardcode emulator connection strings directly in the client construction code. Since these applications don't have config files to modify, the connection string must be in the source code itself to be committed to git. + +**Why hardcoding is required:** +- No config framework means no config files to modify +- Environment variables aren't committed to git, so they won't be reverted by the restore commit +- The auth commit must contain a reversible change in tracked files + +**Java example:** + +**Before:** +```java +public class BlobServiceClientProvider { + private static BlobServiceClient blobServiceClient; + + public static BlobServiceClient getClient() { + if (blobServiceClient == null) { + String endpoint = System.getenv("AZURE_STORAGE_ENDPOINT"); + blobServiceClient = new BlobServiceClientBuilder() + .endpoint(endpoint) + .credential(new DefaultAzureCredentialBuilder().build()) + .buildClient(); + } + return blobServiceClient; + } +} +``` + +**After:** +```java +public class BlobServiceClientProvider { + private static BlobServiceClient blobServiceClient; + + public static BlobServiceClient getClient() { + if (blobServiceClient == null) { + // Hardcoded connection string for smoke testing with Azurite emulator + String smokeTestConnectionString = "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://localhost:10000/devstoreaccount1;"; + + if (smokeTestConnectionString != null && !smokeTestConnectionString.isEmpty()) { + blobServiceClient = new BlobServiceClientBuilder() + .connectionString(smokeTestConnectionString) + .buildClient(); + } else { + // Fallback to original Managed Identity auth + String endpoint = System.getenv("AZURE_STORAGE_ENDPOINT"); + blobServiceClient = new BlobServiceClientBuilder() + .endpoint(endpoint) + .credential(new DefaultAzureCredentialBuilder().build()) + .buildClient(); + } + } + return blobServiceClient; + } +} +``` + +**CRITICAL:** Only the source code changes are included in the auth commit. No config file changes because there are no config files. + +### Non-Emulatable Services + +**Strategy:** Disable via config or conditional guards: + +**Spring Boot example:** +```yaml +# application-smoke.yml +spring: + cloud: + azure: + keyvault: + secret: + enabled: false +``` + +If the app cannot start without the service, recommend Layer 3 (Azure integration tests). + +## Emulator Connection String Values + +Since `docker-compose.smoke.yml` uses **fixed host port mappings**, each emulator's connection string is a **static constant** — deterministic at generation time. + +| Service | Auth Type | How to Determine the Value | +|---------|-----------|----------------------------| +| Azurite (Blob/Queue/Table) | Connection string | Built from well-known account `devstoreaccount1`, well-known key, and mapped ports (10000/10001/10002). See [Azurite docs](https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite#well-known-storage-account-and-key). | +| Service Bus | Connection string | Built from well-known SAS key + `UseDevelopmentEmulator=true`. See [Service Bus emulator docs](https://learn.microsoft.com/en-us/azure/service-bus-messaging/test-locally-with-service-bus-emulator). | +| Event Hubs | Connection string | Same pattern as Service Bus. See [Event Hubs emulator docs](https://learn.microsoft.com/en-us/azure/event-hubs/test-locally-with-event-hub-emulator). | +| Cosmos DB | Endpoint + key | Built from well-known emulator key + mapped port. See [Cosmos emulator docs](https://learn.microsoft.com/en-us/azure/cosmos-db/emulator). Also requires SSL trust store setup. | + +**Important:** The agent must look up the **actual current default credentials** from the linked official docs at generation time. Emulator defaults may change across versions. Do NOT hardcode connection strings from this file. + +## Decision Flow + +``` +For each Azure dependency: +├─ Has local emulator? → Add to docker-compose.smoke.yml → Modify auth to use emulator +├─ Lazy / on-demand? → Skip. Done. +├─ Can disable via config? → Disable in config override or code modification +└─ None of above → Report: "Layer 2 not feasible — use Layer 3" +``` + +## Modification Rules + +When modifying application code or config to use emulators: + +1. **Minimal changes only** — Only modify what's needed to switch auth from Managed Identity to emulator connection strings. Do not refactor, rename, or restructure. +2. **No new dependencies** — Use only packages already in the project. +3. **Reversible** — Changes should be easily revertable to restore original Managed Identity auth. +4. **No unrelated changes** — Never make refactoring, cleanup, or feature changes alongside auth modifications. diff --git a/.github/skills/integration-tests/references/azure-servicebus-testcontainers.md b/.github/skills/integration-tests/references/azure-servicebus-testcontainers.md new file mode 100644 index 000000000..50b596322 --- /dev/null +++ b/.github/skills/integration-tests/references/azure-servicebus-testcontainers.md @@ -0,0 +1,688 @@ +# Azure Service Bus Emulator TestContainers Coding Reference + +## Contents +- 1. MANDATORY - Choose Approach based on Spring/non-Spring, Spring Boot version, TestContainers version +- 2. MANDATORY - Scan all queues/topics/subscriptions before writing code +- 3. DO NOT mock +- 4. Example of Service Bus Emulator config.json +- 5. Spring Cloud Azure Version Compatibility +- 6. Emulator with ServiceBusEmulatorContainer (testcontainers >= 1.20.5) + + Setting up a network + + Starting a SQL Server container as dependency + + Starting a Service Bus Emulator container + + Using Azure Service Bus clients +- 7. Emulator with GenericContainer (testcontainers < 1.20.5) +- 8. Using Spring Cloud Azure with @ServiceConnection (Spring Boot >= 3.1) + + Container setup with @ServiceConnection + + Spring messaging pattern (ServiceBusTemplate / ServiceBusSenderClient) +- 9. Spring Boot 2.x Pattern +- 10. Handle ServiceBusAdministrationClient +- 11. Emulator Readiness and Retry +- 12. Emulator fails to start +- 13. Common Gotchas + +## MANDATORY - Choose Approach based on Spring/non-Spring, Spring Boot version, TestContainers version + +**You MUST check the project's testcontainers and Spring Boot versions BEFORE writing any Service Bus test code.** Using the wrong approach wastes significant time on compilation and runtime failures that are impossible to fix. + +- If project contains usage of `ServiceBusAdministrationClient`, use Service Bus emulator +- If Spring Boot **>= 3.1**, use `@ServiceConnection` for auto-wiring: [Using Spring Cloud Azure with @ServiceConnection](#using-spring-cloud-azure-with-serviceconnection-spring-boot--31) +- If Spring Boot **2.x**, use `@DynamicPropertySource` — `@ServiceConnection` is NOT available: [Spring Boot 2.x pattern](#spring-boot-2x-pattern) +- If testcontainers **>= 1.20.5**, use `ServiceBusEmulatorContainer` from `org.testcontainers:azure`: [Emulator with ServiceBusEmulatorContainer](#emulator-with-servicebusemulatorcontainer-testcontainers--1205) +- If testcontainers **< 1.20.5**, use `GenericContainer` with the emulator Docker image: [Emulator with GenericContainer](#emulator-with-genericcontainer-testcontainers--1205) + +**CRITICAL: `ServiceBusEmulatorContainer` does NOT exist in testcontainers before 1.20.5.** If you see `ClassNotFoundException` or `cannot find symbol: class ServiceBusEmulatorContainer`, the project's testcontainers version is too old. Switch to the GenericContainer approach or upgrade testcontainers. The Maven artifact is `org.testcontainers:azure` (NOT `testcontainers-azure`). + +## MANDATORY - Scan all queues/topics/subscriptions before writing code +The Service Bus emulator cannot create Service Bus queues/topics/subscriptions on the fly, so every resource need to be declared in the config.json. So scan the project to see what queues/topics/subscriptions need to be declared. And then define them in config.json. + +## Do NOT mock +**NEVER MOCK** the `ServiceBusSenderClient`, `ServiceBusTemplate`, `ServiceBusReceiverClient`, `ServiceBusProcessorClient`, `TokenCredential`, `ServiceBusAdministrationClient`, or **ANY bean related to the migrated service.** + +## Example of Service Bus Emulator config.json +Place this file at `src/test/resources/config.json` (or `service-bus-config.json`): + +```json +{ + "UserConfig": { + "Namespaces": [ + { + "Name": "sbemulatorns", + "Queues": [ + { + "Name": "queue.1", + "Properties": { + "DeadLetteringOnMessageExpiration": false, + "DefaultMessageTimeToLive": "PT1H", + "DuplicateDetectionHistoryTimeWindow": "PT20S", + "ForwardDeadLetteredMessagesTo": "", + "ForwardTo": "", + "LockDuration": "PT1M", + "MaxDeliveryCount": 3, + "RequiresDuplicateDetection": false, + "RequiresSession": false + } + } + ], + + "Topics": [ + { + "Name": "topic.1", + "Properties": { + "DefaultMessageTimeToLive": "PT1H", + "DuplicateDetectionHistoryTimeWindow": "PT20S", + "RequiresDuplicateDetection": false + }, + "Subscriptions": [ + { + "Name": "subscription.1", + "Properties": { + "DeadLetteringOnMessageExpiration": false, + "DefaultMessageTimeToLive": "PT1H", + "LockDuration": "PT1M", + "MaxDeliveryCount": 3, + "ForwardDeadLetteredMessagesTo": "", + "ForwardTo": "", + "RequiresSession": false + }, + "Rules": [ + { + "Name": "app-prop-filter-1", + "Properties": { + "FilterType": "Correlation", + "CorrelationFilter": { + "ContentType": "application/text", + "CorrelationId": "id1", + "Label": "subject1", + "MessageId": "msgid1", + "ReplyTo": "someQueue", + "ReplyToSessionId": "sessionId", + "SessionId": "session1", + "To": "xyz" + } + } + } + ] + }, + { + "Name": "subscription.2", + "Properties": { + "DeadLetteringOnMessageExpiration": false, + "DefaultMessageTimeToLive": "PT1H", + "LockDuration": "PT1M", + "MaxDeliveryCount": 3, + "ForwardDeadLetteredMessagesTo": "", + "ForwardTo": "", + "RequiresSession": false + }, + "Rules": [ + { + "Name": "user-prop-filter-1", + "Properties": { + "FilterType": "Correlation", + "CorrelationFilter": { + "Properties": { + "prop1": "value1" + } + } + } + } + ] + }, + { + "Name": "subscription.3", + "Properties": { + "DeadLetteringOnMessageExpiration": false, + "DefaultMessageTimeToLive": "PT1H", + "LockDuration": "PT1M", + "MaxDeliveryCount": 3, + "ForwardDeadLetteredMessagesTo": "", + "ForwardTo": "", + "RequiresSession": false + } + }, + { + "Name": "subscription.4", + "Properties": { + "DeadLetteringOnMessageExpiration": false, + "DefaultMessageTimeToLive": "PT1H", + "LockDuration": "PT1M", + "MaxDeliveryCount": 3, + "ForwardDeadLetteredMessagesTo": "", + "ForwardTo": "", + "RequiresSession": false + }, + "Rules": [ + { + "Name": "sql-filter-1", + "Properties": { + "FilterType": "Sql", + "SqlFilter": { + "SqlExpression": "sys.MessageId = '123456' AND userProp1 = 'value1'" + }, + "Action" : { + "SqlExpression": "SET sys.To = 'Entity'" + } + } + } + ] + } + ] + } + ] + } + ], + "Logging": { + "Type": "File" + } + } +} +``` + +## Spring Cloud Azure Version Compatibility + +Pick the Spring Cloud Azure version compatible with the project's Spring Boot version. See [aka.ms/spring/versions](https://aka.ms/spring/versions) for the compatibility matrix. + +| Spring Boot | Spring Cloud Azure | Notes | +|---|---|---| +| 2.x | 4.x | No `@ServiceConnection`, no `spring-cloud-azure-testcontainers`. Use `@DynamicPropertySource`. | +| 3.1.x - 3.5.x | 5.x | `@ServiceConnection` available | +| 4.0.x | 7.x | `@ServiceConnection` available | + +## Emulator with ServiceBusEmulatorContainer (testcontainers >= 1.20.5) + +**Requires: `org.testcontainers:azure` version >= 1.20.5** + +```xml + + org.testcontainers + azure + 1.20.5 + test + + + org.testcontainers + mssqlserver + test + +``` + +### Setting up a network + +```java +Network network = Network.newNetwork(); +``` + +### Starting a SQL Server container as dependency + +```java +MSSQLServerContainer mssqlServerContainer = new MSSQLServerContainer<>( + "mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04" +) + .acceptLicense() + .withPassword("yourStrong(!)Password") + .withCreateContainerCmdModifier(cmd -> { + cmd.getHostConfig().withCapAdd(Capability.SYS_PTRACE); + }) + .withNetwork(network); +``` + +### Starting a Service Bus Emulator container + +```java +ServiceBusEmulatorContainer emulator = new ServiceBusEmulatorContainer( + "mcr.microsoft.com/azure-messaging/servicebus-emulator:1.1.2" +) + .acceptLicense() + .withConfig(MountableFile.forClasspathResource("/service-bus-config.json")) + .withNetwork(network) + .withMsSqlServerContainer(mssqlServerContainer); +``` + +### Using Azure Service Bus clients + +```java +ServiceBusSenderClient senderClient = new ServiceBusClientBuilder() + .connectionString(emulator.getConnectionString()) + .sender() + .queueName("queue.1") + .buildClient(); + +ServiceBusProcessorClient processorClient = new ServiceBusClientBuilder() + .connectionString(emulator.getConnectionString()) + .processor() + .queueName("queue.1") + .processMessage(messageConsumer) + .processError(errorConsumer) + .buildProcessorClient(); +``` + +## Emulator with GenericContainer (testcontainers < 1.20.5) + +**Use this when the project's testcontainers version does not include `ServiceBusEmulatorContainer`.** This approach uses `GenericContainer` directly with the same emulator Docker image. + +```xml + + org.testcontainers + testcontainers + test + + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + mssqlserver + test + +``` + +```java +@Testcontainers +@Tag("Layer1") +class ServiceBusEmulatorL1Test { + + static final Network NETWORK = Network.newNetwork(); + + @Container + static final MSSQLServerContainer SQL_SERVER = new MSSQLServerContainer<>( + "mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04") + .acceptLicense() + .withPassword("yourStrong(!)Password") + .withNetwork(NETWORK) + .withNetworkAliases("mssql"); + + @Container + static final GenericContainer SERVICE_BUS = new GenericContainer<>( + "mcr.microsoft.com/azure-messaging/servicebus-emulator:1.1.2") + .withExposedPorts(5672) + .withNetwork(NETWORK) + .withEnv("ACCEPT_EULA", "Y") + .withEnv("MSSQL_SA_PASSWORD", "yourStrong(!)Password") + .withEnv("SQL_SERVER", "mssql") // network alias of the SQL Server container + .withCopyFileToContainer( + MountableFile.forClasspathResource("config.json"), + "/ServiceBus_Emulator/ConfigFiles/config.json") + .dependsOn(SQL_SERVER) + .waitingFor(Wait.forLogMessage(".*Emulator Service is Successfully Up!.*", 1) + .withStartupTimeout(Duration.ofMinutes(3))); + + static String getConnectionString() { + return String.format( + "Endpoint=sb://%s:%d;SharedAccessKeyName=RootManageSharedAccessKey;" + + "SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;", + SERVICE_BUS.getHost(), SERVICE_BUS.getMappedPort(5672)); + } + + private ServiceBusSenderClient senderClient; + private ServiceBusReceiverClient receiverClient; + + @BeforeAll + static void waitForEmulator() { + // Emulator needs extra time after port is ready for entities to initialize + // Use Awaitility — NEVER Thread.sleep() + Awaitility.await() + .atMost(Duration.ofSeconds(120)) + .pollInterval(Duration.ofSeconds(2)) + .until(() -> { + try { + ServiceBusSenderClient probe = new ServiceBusClientBuilder() + .connectionString(getConnectionString()) + .sender().queueName("queue.1").buildClient(); + probe.sendMessage(new ServiceBusMessage("probe")); + probe.close(); + return true; + } catch (Exception e) { + return false; + } + }); + } + + @BeforeEach + void setupClients() { + senderClient = new ServiceBusClientBuilder() + .connectionString(getConnectionString()) + .sender().queueName("queue.1").buildClient(); + receiverClient = new ServiceBusClientBuilder() + .connectionString(getConnectionString()) + .receiver().queueName("queue.1").buildClient(); + } + + @AfterEach + void closeClients() { + if (senderClient != null) senderClient.close(); + if (receiverClient != null) receiverClient.close(); + } +} +``` + +## Using Spring Cloud Azure with @ServiceConnection (Spring Boot >= 3.1) + +**Requires:** Spring Boot >= 3.1, `spring-cloud-azure-testcontainers`, testcontainers >= 1.20.5. + +`@ServiceConnection` is a Spring Boot 3.1+ feature. **Do NOT use this with Spring Boot 2.x — it will not compile.** For Spring Boot 2.x, see the [Spring Boot 2.x pattern](#spring-boot-2x-pattern) section. + +```xml + + + 5.25.0 + + + + + + com.azure.spring + spring-cloud-azure-dependencies + ${version.spring.cloud.azure} + pom + import + + + + + + + + com.azure.spring + spring-messaging-azure-servicebus + + + + + com.azure.spring + spring-cloud-azure-starter + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-testcontainers + test + + + org.testcontainers + junit-jupiter + test + + + com.azure.spring + spring-cloud-azure-testcontainers + test + + + com.microsoft.sqlserver + mssql-jdbc + test + + +``` + +### Container setup with @ServiceConnection + +`@ServiceConnection` tells Spring Boot to auto-configure `AzureServiceBusConnectionDetails` from the running container — no manual connection string wiring needed. The `MSSQLServerContainer` is NOT annotated with `@Container` because `ServiceBusEmulatorContainer.withMsSqlServerContainer()` manages its lifecycle. + +```java +private static final Network NETWORK = Network.newNetwork(); + +private static final MSSQLServerContainer SQLSERVER = new MSSQLServerContainer<>( + "mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04") + .acceptLicense() + .withNetwork(NETWORK) + .withNetworkAliases("sqlserver"); + +@Container +@ServiceConnection +private static final ServiceBusEmulatorContainer SERVICE_BUS = new ServiceBusEmulatorContainer( + "mcr.microsoft.com/azure-messaging/servicebus-emulator:latest") + .acceptLicense() + .withCopyFileToContainer(MountableFile.forClasspathResource("config.json"), + "/ServiceBus_Emulator/ConfigFiles/config.json") + .withNetwork(NETWORK) + .withMsSqlServerContainer(SQLSERVER); +``` + +### Spring messaging pattern (ServiceBusTemplate / ServiceBusSenderClient) + +Use when the application uses `ServiceBusSenderClient` or `ServiceBusTemplate` for sending, and `ServiceBusRecordMessageListener` for processing. + +```java +@SpringJUnitConfig +@TestPropertySource(properties = { + "spring.cloud.azure.servicebus.entity-name=queue.1", + "spring.cloud.azure.servicebus.entity-type=queue" +}) +@Testcontainers +@Tag("Layer1") +class ServiceBusMessagingL1Test { + + // ... container setup as above ... + + @Autowired + private ServiceBusSenderClient senderClient; + + @Autowired + private ServiceBusTemplate serviceBusTemplate; + + @Test + void senderClientCanSendAndReceiveMessage() { + // The emulator depends on SQL Server and needs time to initialize messaging entities + waitAtMost(Duration.ofSeconds(120)).pollInterval(Duration.ofSeconds(2)).untilAsserted(() -> { + senderClient.sendMessage(new ServiceBusMessage("Hello World!")); + }); + + waitAtMost(Duration.ofSeconds(30)).untilAsserted(() -> { + assertThat(Config.MESSAGES).contains("Hello World!"); + }); + } + + @Test + void serviceBusTemplateCanSendAndReceiveMessage() { + waitAtMost(Duration.ofSeconds(120)).pollInterval(Duration.ofSeconds(2)).untilAsserted(() -> { + serviceBusTemplate.sendAsync("queue.1", + MessageBuilder.withPayload("Hello from template!").build()) + .block(Duration.ofSeconds(10)); + }); + + waitAtMost(Duration.ofSeconds(30)).untilAsserted(() -> { + assertThat(Config.MESSAGES).contains("Hello from template!"); + }); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(classes = { + AzureGlobalPropertiesAutoConfiguration.class, + AzureServiceBusAutoConfiguration.class, + AzureServiceBusMessagingAutoConfiguration.class}) + static class Config { + + private static final Set MESSAGES = ConcurrentHashMap.newKeySet(); + + @Bean + ServiceBusRecordMessageListener processMessage() { + return context -> MESSAGES.add(context.getMessage().getBody().toString()); + } + + @Bean + ServiceBusErrorHandler errorHandler() { + return (context) -> { }; + } + } +} +``` + +## Spring Boot 2.x Pattern + +**Spring Boot 2.x does NOT support `@ServiceConnection` or `spring-boot-testcontainers`.** Use `@DynamicPropertySource` to inject the emulator connection string into the Spring context. + +```java +@SpringBootTest +@ActiveProfiles("test") // MANDATORY +@Testcontainers +@Tag("Layer1") +class MessageServiceL1Test { + + static final Network NETWORK = Network.newNetwork(); + + @Container + static final MSSQLServerContainer SQL_SERVER = new MSSQLServerContainer<>( + "mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04") + .acceptLicense() + .withPassword("yourStrong(!)Password") + .withNetwork(NETWORK) + .withNetworkAliases("mssql"); + + @Container + static final GenericContainer SERVICE_BUS = new GenericContainer<>( + "mcr.microsoft.com/azure-messaging/servicebus-emulator:1.1.2") + .withExposedPorts(5672) + .withNetwork(NETWORK) + .withEnv("ACCEPT_EULA", "Y") + .withEnv("MSSQL_SA_PASSWORD", "yourStrong(!)Password") + .withEnv("SQL_SERVER", "mssql") + .withCopyFileToContainer( + MountableFile.forClasspathResource("config.json"), + "/ServiceBus_Emulator/ConfigFiles/config.json") + .dependsOn(SQL_SERVER) + .waitingFor(Wait.forLogMessage(".*Emulator Service is Successfully Up!.*", 1) + .withStartupTimeout(Duration.ofMinutes(3))); + + // NO @MockBean — all beans wired from the real emulator + @DynamicPropertySource + static void serviceBusProperties(DynamicPropertyRegistry registry) { + String connectionString = String.format( + "Endpoint=sb://%s:%d;SharedAccessKeyName=RootManageSharedAccessKey;" + + "SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;", + SERVICE_BUS.getHost(), SERVICE_BUS.getMappedPort(5672)); + registry.add("spring.cloud.azure.servicebus.connection-string", () -> connectionString); + registry.add("spring.cloud.azure.servicebus.namespace", () -> "sbemulatorns"); + // Disable managed identity — use the emulator connection string instead + registry.add("spring.cloud.azure.credential.managed-identity-enabled", () -> "false"); + } + + @Autowired + private MessageService messageService; // The app's own service class + + @Test + void sendMessageThroughApplicationService() { + // ALWAYS test through the application's own classes, not the SDK directly + Awaitility.await() + .atMost(Duration.ofSeconds(120)) + .pollInterval(Duration.ofSeconds(2)) + .untilAsserted(() -> { + messageService.sendMessage("test-queue", "Hello from test!"); + }); + } +} +``` + +## Handle ServiceBusAdministrationClient + +**CRITICAL: The Service Bus emulator does NOT support the ServiceBusAdministrationClient REST API.** The emulator only exposes AMQP on port 5672 for messaging operations. The `ServiceBusAdministrationClient` uses HTTPS REST API (port 443) which the emulator does not provide. Any call to `adminClient.getTopic()`, `adminClient.createSubscription()`, etc. will fail with `Connection refused: localhost:443`. + +### When the Application Uses ServiceBusAdministrationClient + +If the application has beans that use `ServiceBusAdministrationClient`, you MUST: +1. Use a test configuration that doesn't include the admin client. +2. **Include ALL dependent beans** - if `TopicProperties mainExchangeTopic` depends on `adminClient`, you must exclude BOTH +3. **Pre-create topics/queues/subscriptions in config.json** instead of relying on admin API calls + +### Complete Test Configuration Example + +When the application has code like this: + +```java +// Production code +@Bean +public ServiceBusAdministrationClient adminClient(AzureServiceBusProperties properties, TokenCredential credential) { + return new ServiceBusAdministrationClientBuilder() + .credential(properties.getFullyQualifiedNamespace(), credential) + .buildClient(); +} + +@Bean +public TopicProperties mainExchangeTopic(ServiceBusAdministrationClient adminClient) { + try { + return adminClient.getTopic(MAIN_EXCHANGE); // ← This FAILS against emulator! + } catch (ResourceNotFoundException e) { + return adminClient.createTopic(MAIN_EXCHANGE); + } +} +``` +**You MUST Exclude Admin Beans with @Profile:** + +```java +// Production code - mark with profile exclusion +@Bean +@Profile("!test") // Excluded in test profile +public ServiceBusAdministrationClient adminClient(AzureServiceBusProperties properties, TokenCredential credential) { + // ... +} + +@Bean +@Profile("!test") // Excluded in test profile +public TopicProperties mainExchangeTopic(ServiceBusAdministrationClient adminClient) { + // ... +} +``` + +**IMPORTANT:** Pre-create the topic in your `service-bus-config.json`: + +```json +{ + "UserConfig": { + "Namespaces": [{ + "Name": "sbemulatorns", + "Topics": [{ + "Name": "mainExchange", + "Subscriptions": [/* pre-create any needed subscriptions */] + }] + }] + } +} +``` + +## Emulator Readiness and Retry + +The Service Bus emulator takes significantly longer to start than Azurite because it depends on SQL Server initializing messaging entities. **Always use Awaitility retry/polling for initial send operations — never `Thread.sleep()`.** + +```java +// Typical total time from container start to first successful send: 60-120 seconds +waitAtMost(Duration.ofSeconds(120)) + .pollInterval(Duration.ofSeconds(2)) + .untilAsserted(() -> { + senderClient.sendMessage(new ServiceBusMessage("test")); + }); +``` + +## Emulator fails to start +If the emulator won't start (Docker not available, timeout, resource limits), **do NOT fall back to mocking.** Instead: +1. Increase the startup timeout (the emulator needs 60-120s) +2. Check Docker resources (emulator + SQL Server need ~4GB RAM) +3. Verify config.json is correctly mounted +4. Check the emulator logs for specific errors + +## Common Gotchas + +| Problem | Cause | Fix | +|---------|-------|-----| +| `ClassNotFoundException: ServiceBusEmulatorContainer` | testcontainers version < 1.20.5 | Upgrade to >= 1.20.5 OR use GenericContainer approach | +| `cannot find symbol: @ServiceConnection` | Spring Boot < 3.1 | Use `@DynamicPropertySource` instead (see Spring Boot 2.x pattern) | +| `UnsatisfiedDependencyException: TokenCredential` | Custom `@Configuration` creates Azure beans without correct emulator properties | Add `@ActiveProfiles("test")` AND provide emulator connection string via `@DynamicPropertySource` so all beans connect to the real emulator | +| `ServiceBusException: Entity not found` | Emulator entities not yet initialized | Use `waitAtMost` retry pattern with Awaitility | +| `Connection refused on port 5672` | SQL Server not ready when emulator starts | Use `dependsOn(SQL_SERVER)` + `waitingFor` log message strategy | +| `config.json not found` | Wrong mount path | Ensure `MountableFile.forClasspathResource("config.json")` matches file in `src/test/resources/` | +| Queue/topic name mismatch | config.json names don't match test properties | Verify `entity-name` matches config.json `Name` field | +| `No qualifying bean of type ServiceBusSenderClient` | Missing auto-configuration imports | Add `@ImportAutoConfiguration` with `AzureGlobalPropertiesAutoConfiguration`, `AzureServiceBusAutoConfiguration`, `AzureServiceBusMessagingAutoConfiguration` | +| `Checkpointer is null` | `auto-complete` not disabled | Set `spring.cloud.stream.servicebus.bindings.-in-0.consumer.auto-complete=false` | +| `InvalidDestinationException` on context start | Test context loads production JMS `@Configuration` that connects to fake endpoint | Add `@ActiveProfiles("test")` and provide correct emulator connection properties via `@DynamicPropertySource` | +| Tests hang during context startup | `@SpringBootTest` loads full context with real Azure/DB connections | Add `@ActiveProfiles("test")` and provide all emulator properties via `@DynamicPropertySource`, or narrow context with `classes = {...}` | diff --git a/.github/skills/integration-tests/references/azure-storage-testcontainers.md b/.github/skills/integration-tests/references/azure-storage-testcontainers.md new file mode 100644 index 000000000..4449ca75c --- /dev/null +++ b/.github/skills/integration-tests/references/azure-storage-testcontainers.md @@ -0,0 +1,276 @@ +# Azure Blob Storage with Azurite TestContainers Coding Reference + +## Contents +- 1.Azurite-Specific Self-Checks +- 2.Azurite Container Setup + + Azurite well-known credentials +- 3.Azurite URL Format Gotcha +- 4.Spring Boot Config Override for Blob Storage + + ⚠️ CRITICAL: `System.getenv()` vs Spring Properties Mismatch +- 5. Shared Azurite Base Class +- 6. Azure Blob-Specific Test Patterns + + Testing Application Code (Not the SDK) + + Asserting on Behavior, Not Structure + + Error Handling with BlobStorageException + +## 1.Azurite-Specific Self-Checks +Before running tests, verify: +- [ ] If any application code reads config via `System.getenv()`, you've provided values through constructor injection or `@TestConfiguration` beans — NOT through `@DynamicPropertySource` (which only sets Spring properties) +- [ ] If the application parses URLs to extract container names, you've handled the Azurite URL format difference (see section 2) +- [ ] No test uses `DefaultAzureCredentialBuilder` — use `StorageSharedKeyCredential` with Azurite dev credentials +- [ ] No test checks for `blob.core.windows.net` in URLs — Azurite URLs use `localhost` +- [ ] `@ActiveProfiles("test")` is on every `@SpringBootTest` class (if using Spring Boot) +- [ ] `@BeforeEach` cleanup deletes all blob containers to prevent cross-test contamination + +## 2.Azurite Container Setup +```java +new GenericContainer<>("mcr.microsoft.com/azure-storage/azurite:latest") + .withExposedPorts(10000) + .withCommand("azurite-blob", "--blobHost", "0.0.0.0") + .waitingFor(Wait.forListeningPort()); // REQUIRED — prevents race conditions +``` + +### Azurite well-known credentials (safe for local testing only) +- Account name: `devstoreaccount1` +- Account key: `Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==` + +## 3.Azurite URL Format Gotcha +**IMPORTANT — Azurite URL format differs from production Azure Blob Storage:** + +- **Production Azure:** `https://.blob.core.windows.net//` +- **Azurite:** `http://:/devstoreaccount1//` + +If the application code parses Azure Blob Storage URLs (e.g., extracting container name from the URL path), the Azurite URL will have an extra `/devstoreaccount1/` path segment that production URLs don't have. This causes: +- Container name extracted as `devstoreaccount1` instead of the actual container name +- URL-based detection logic (e.g., checking for `blob.core.windows.net`) failing for Azurite URLs +- Path indices being off-by-one when splitting URLs into segments + +**How to handle this — choose the approach that requires the LEAST production code change:** + +1. **Best approach — Inject the container name and BlobServiceClient separately** instead of having the application parse them from a URL: + ```java + // Add a test-friendly constructor that accepts pre-built client + container name + // Minimal source change: 3-4 lines + S3FileSystemStore(String rootPath, BlobServiceClient client, String containerName) { + this.blobServiceClient = client; + this.containerName = containerName; + this.rootPath = rootPath; + } + ``` + +2. **If the application detects Azure by checking for `blob.core.windows.net`** in the URL (common pattern), provide an override mechanism so tests can force Azure mode: + ```java + // Add a boolean flag or config property to bypass URL detection + S3FileSystemStore(String rootPath, FileSystem fs, BlobServiceClient client, boolean isAzure) { + this.blobServiceClient = client; + this.isAzureMode = isAzure; // skip blob.core.windows.net check + } + ``` +3. **If the application splits the URL path to extract container names**, the path indices will be different for Azurite vs Azure: + - Azure URL path: `//` -> `pathParts[1]` = container name + - Azurite URL path: `/devstoreaccount1//` -> `pathParts[1]` = `devstoreaccount1`, `pathParts[2]` = container name + + **Never assume Azurite URLs have the same path structure as production Azure URLs.** Instead, pass the container name as a parameter to avoid URL parsing entirely. + +**Self-check:** Before running tests, ask "Does any application code parse URLs to extract container names or detect Azure mode?" If yes, you MUST handle the Azurite URL format difference or 60%+ of tests will fail at runtime. + +## 4.Spring Boot Config Override for Blob Storage + +Production code often uses `DefaultAzureCredentialBuilder` which requires HTTPS and real Azure endpoints. Tests use local Azurite over HTTP with well-known keys. You **MUST** override the production bean configuration so the test context starts successfully. + +**Step 1 — ALWAYS add `@ActiveProfiles("test")` to every `@SpringBootTest` class:** +```java +@SpringBootTest +@ActiveProfiles("test") // MANDATORY — prevents production @Profile("!test") beans from loading +@Testcontainers +class MyServiceL1Test { ... } +``` +Without `@ActiveProfiles("test")`, production configuration classes (DataSource configs, credential builders, startup initializers) will load and fail because they try to connect to real services. This is the **#1 cause of Spring context startup failures** in integration tests. + +**Step 2 — use `@TestConfiguration` with `@Primary` to override production beans:** + +```java +@TestConfiguration +public class TestBlobStorageConfig { + // Override the production BlobServiceClient that uses DefaultAzureCredential + @Bean + @Primary + public BlobServiceClient blobServiceClient( + @Value("${azure.storage.endpoint}") String endpoint) { + // Azurite well-known credentials — safe for local testing only + StorageSharedKeyCredential credential = new StorageSharedKeyCredential( + "devstoreaccount1", + "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="); + return new BlobServiceClientBuilder() + .endpoint(endpoint) + .credential(credential) + .buildClient(); + } +} +``` + +**Step 3 — narrow the Spring context when full context is too heavy:** +If `@SpringBootTest` loads too many unrelated beans (startup initializers, schedulers, external integrations) that fail during context startup, use context slicing: +```java +@SpringBootTest(classes = {MyService.class, TestBlobStorageConfig.class}) +``` +This loads only the beans you need for testing, avoiding failures from unrelated components. + +### ⚠️ CRITICAL: `System.getenv()` vs Spring Properties Mismatch + +**`@DynamicPropertySource` sets Spring properties — it does NOT set environment variables.** If the application's DAO/service reads configuration via `System.getenv("AZURE_STORAGE_ENDPOINT")`, setting `registry.add("AZURE_STORAGE_ENDPOINT", ...)` in `@DynamicPropertySource` will NOT work. The DAO will receive `null` and fail with `NullPointerException` before any test runs. + +**How to detect this:** Before writing any test, `grep -r "System.getenv\|System.getProperty" src/main/` to find all environment variable reads in the application code. If you find any, you MUST use one of the approaches below. + +**Fix — choose the approach that requires the LEAST production code change:** + +1. **Best approach — Add a constructor that accepts the dependencies directly** (minimal safe source change, 3-5 lines): + ```java + // Add to the production DAO class: + AzureBlobDigitalMediaDAO(BlobServiceClient client, String containerName) { + this.blobServiceClient = client; + this.containerName = containerName; + } + ``` + Then in the test, provide the client via `@TestConfiguration` with `@Primary`: + ```java + @TestConfiguration + static class TestConfig { + @Bean @Primary + public AzureBlobDigitalMediaDAO testDao() { + return new AzureBlobDigitalMediaDAO(blobServiceClient, CONTAINER_NAME); + } + } + ``` + +2. **Alternative — Use `@TestConfiguration` `@Primary` bean + `@ConditionalOnMissingBean`** on the production config class so the test bean takes precedence. + +3. **Last resort — Set actual environment variables via `System.setProperty()` paired with changing production code from `System.getenv()` to `System.getProperty()`** (slightly larger source change but avoids constructor changes). + +**Anti-pattern — DO NOT DO THIS:** +```java +// WRONG: @DynamicPropertySource does NOT populate System.getenv() +@DynamicPropertySource +static void props(DynamicPropertyRegistry registry) { + registry.add("AZURE_STORAGE_ENDPOINT", () -> azuriteEndpoint); // ❌ DAO calls System.getenv(), not Spring +} +``` + +**Self-check:** For every `System.getenv()` call in the production code, verify your test provides the value through a mechanism the production code actually reads. `@DynamicPropertySource` only works for code that reads from Spring's `Environment` (e.g., `@Value`, `@ConfigurationProperties`). + +**Key rules:** +- ALWAYS add `@ActiveProfiles("test")` to every `@SpringBootTest` test class — this is the most common mistake. +- NEVER use `DefaultAzureCredentialBuilder` in tests — it will fail on HTTP endpoints and in CI. +- ALWAYS use `StorageSharedKeyCredential` with Azurite's well-known dev credentials for Blob Storage tests. +- Provide test properties via `@DynamicPropertySource` from the TestContainers container (e.g., `azuriteContainer.getHost()` + `azuriteContainer.getMappedPort(10000)`). +- If the full `@SpringBootTest` context fails to start, narrow it with `classes = {...}` rather than trying to mock/stub all failing beans. + +## 5.Shared Azurite Base Class + +When you have 2+ test classes using Azurite, extract the container declaration and client construction into a shared abstract base class: + +```java +// Shared base class — declared once, reused by all test classes +@Testcontainers +abstract class AbstractAzuriteL1Test { + @Container + static final GenericContainer azurite = new GenericContainer<>( + "mcr.microsoft.com/azure-storage/azurite:latest") + .withExposedPorts(10000) + .withCommand("azurite-blob", "--blobHost", "0.0.0.0"); + + protected static BlobServiceClient blobServiceClient; + + @BeforeAll + static void initClient() { + String endpoint = String.format("http://%s:%d/devstoreaccount1", + azurite.getHost(), azurite.getMappedPort(10000)); + blobServiceClient = new BlobServiceClientBuilder() + .connectionString(getAzuriteConnectionString()) + .buildClient(); + } +} + +// Each test class extends the base — no duplicated container setup +@Tag("Layer1") +class EventHandlerL1Test extends AbstractAzuriteL1Test { ... } +class S3ClientUtilL1Test extends AbstractAzuriteL1Test { ... } +``` + +## 6.Azure Blob-Specific Test Patterns + +### Testing Application Code (Not the SDK) + +**Anti-pattern — DO NOT DO THIS:** +```java +// WRONG: Test calls Azure SDK directly instead of the application's CreateBucket class +@Test void testCreateBucketClass() { + // This tests the Azure SDK, NOT the application code + blobServiceClient.createBlobContainer("test-container"); + assertTrue(blobServiceClient.getBlobContainerClient("test-container").exists()); +} +``` + +**Anti-pattern — DO NOT DO THIS either:** +```java +// WRONG: Tests individual Azure Blob operations instead of the application's handler +@Test void testUploadBlob() { + BlobContainerClient container = blobServiceClient.getBlobContainerClient("test"); + container.create(); + container.getBlobClient("file.txt").upload(BinaryData.fromString("data"), true); + String content = container.getBlobClient("file.txt").downloadContent().toString(); + assertEquals("data", content); + // This verifies Azure SDK works — it does NOT test the application's EventHandler +} +``` + +**Correct pattern — invoke the application's own entry points:** +```java +// RIGHT: Test calls the application's own handler method end-to-end +@Test void testHandleRequestProcessesEventsEndToEnd() { + // Arrange — pre-populate Azurite with test event data + uploadTestEvent("container1", "event1.txt", "status:SHIPPED\ntimestamp:1573410202"); + + // Act — invoke the APPLICATION's handler, not the SDK + EventHandler handler = new EventHandler(blobServiceClient); + handler.handleRequest(mockContext); + + // Assert — verify the application produced the expected output + String summary = downloadBlob("summary-container", "summary.txt"); + assertThat(summary).contains("SHIPPED"); +} +``` + +### Asserting on Behavior, Not Structure + +**Every write test must read the data back and verify content.** If you write a blob to Azurite, download it and assert the content matches — do NOT just assert that the container exists (which is always true if you created it in `@BeforeEach`). A test whose only assertion is `assertTrue(container.exists())` or `assertNotNull(result)` is effectively testing nothing. + +### Error Handling with BlobStorageException + +```java +// RIGHT: Test error handling through the application class +@Test void testCreateBucketWithInvalidName() { + RuntimeException ex = assertThrows(RuntimeException.class, + () -> createBucket.createBucketAsync("INVALID-UPPERCASE")); + assertThat(ex.getMessage()).contains("Failed to create bucket"); + assertThat(ex.getCause()).isInstanceOf(BlobStorageException.class); +} +``` + +For each `catch (BlobStorageException e)` block in the migrated code, write a corresponding test: +```java +// If the source code has: +try { + blobClient.upload(data, true); +} catch (BlobStorageException e) { + throw new RuntimeException("Failed to create bucket", e); +} + +// Then you MUST have this test: +@Test void testCreateBucketWrapsStorageException() { + RuntimeException ex = assertThrows(RuntimeException.class, + () -> createBucket.createBucketAsync("INVALID")); + assertThat(ex.getMessage()).contains("Failed to create bucket"); + assertThat(ex.getCause()).isInstanceOf(BlobStorageException.class); +} +``` diff --git a/.github/skills/integration-tests/references/layer1-local-integration.md b/.github/skills/integration-tests/references/layer1-local-integration.md new file mode 100644 index 000000000..2b2d06262 --- /dev/null +++ b/.github/skills/integration-tests/references/layer1-local-integration.md @@ -0,0 +1,358 @@ +# Layer 1: Local Integration Tests + +## Contents +- Prerequisites (Docker & TestContainers available) +- Azure Authentication Strategies for Integration Tests Layer 1 (TokenCredential,Connection Strings, Keys) + + Emulatable Azure Services + + Emulator Connection String Values + + Modification Rules +- Principles + + 1. Core Principles (naming convention, no source code modification to fix TestContainers or Docker compatibility issues) + + 2. MOST IMPORTANT: Test application code, not the SDK + + 3. Make untestable code testable with minimal, safe source changes + + 4. Wire and test the full execution path + + 5. Assert on behavior, not structure + + 6. Only set up what the test uses + + 7. Test edge cases + + 8. Cover downstream consumers + + 9. Only label tests as integration tests if they integrate external systems + + 10. Stay scoped to the migration target + + 11. DO NOT use any test ordering mechanism + + 12. DO NOT use reflection to access or modify private fields,DO NOT widen field visibility + + 13. DO write specific, descriptive assertions + + 14. DO structure every test with clear Arrange-Act-Assert (AAA) sections + + 15. DO add `@BeforeEach` cleanup when tests share a container + + 16. DO verify the test count after `mvn verify` matches expectations + + 17. DO extract shared test infrastructure into a base class or helper when you have 2+ test classes + + 18. DO extract commonly repeated test patterns into helper methods + + 19. DO NOT leave `System.out.println` or debug logging statements in test code + + 20. NEVER use `Thread.sleep()` to wait for containers to start + +## Prerequisites + +- Docker installed and running +- TestContainers library available for the project's language + +## Azure Authentication Strategies for Integration Tests Layer 1 + +Migrated applications typically use **Microsoft Entra ID OAuth** such as **Managed Identity** (`DefaultAzureCredential`) which only works on Azure infrastructure. For local integration testing applications must connect to local emulators. + +### Emulatable Azure Services + +These services have official emulators that can run in Docker containers: + +| Azure Service | Emulator Image | Dependencies | +|---------------|----------------|--------------| +| Blob / Queue / Table Storage | `mcr.microsoft.com/azure-storage/azurite` | None | +| Cosmos DB | `mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator` | None (but requires SSL trust store + endpoint/key auth) | +| SQL Database | `mcr.microsoft.com/mssql/server` | None (wire-compatible with SQL Server) | +| Service Bus | `mcr.microsoft.com/azure-messaging/servicebus-emulator` | Requires MSSQL companion + JSON config | +| Event Hubs | `mcr.microsoft.com/azure-messaging/eventhubs-emulator` | Requires Azurite companion + JSON config | + +> **Note:** Service Bus and Event Hubs emulators require companion containers and a JSON config file. See [azure-servicebus-testcontainers.md](references/azure-servicebus-testcontainers.md) for the config format. + +**Cosmos DB emulator note:** Unlike other emulators which use connection strings, the Cosmos DB emulator authenticates via **endpoint + account key** and requires an **SSL certificate** imported into a trust store (e.g., `javax.net.ssl.trustStore` for Java). If SSL setup is too complex, treat Cosmos DB as non-emulatable and recommend Layer 3. + +### Authentication Modification Strategies (TokenCredential,Connection Strings, Keys) + +For each emulatable Azure dependency, modify how the application authenticates: + +#### Config-Driven Applications + +**Examples:** Spring Boot applications with `application.properties` or `application.yml`, Quarkus/Micronaut applications with `application.properties`. + +**Strategy:** Inject the connection string via test configurations, like property source. + +**CRITICAL:** You MUST always modify the config file to add the explicit connection string value. Do NOT rely on environment variables. + +#### Hardcoded/Plain Applications + +**Examples:** Plain Java CLI apps without config frameworks + +**When to use:** Applications that have NO configuration framework (no Spring Boot, no application.properties). These apps construct clients directly in code with hardcoded values. + +**Strategy:** Modify the source code to extend the client constructor which accepts hardcode emulator connection strings. Since these applications don't have config files to modify, the connection string must be in the source code itself to be committed to git. + +**Java example:** + +**Before:** +```java +public class BlobServiceClientProvider { + private static BlobServiceClient blobServiceClient; + + public static BlobServiceClient getClient() { + if (blobServiceClient == null) { + String endpoint = System.getenv("AZURE_STORAGE_ENDPOINT"); + blobServiceClient = new BlobServiceClientBuilder() + .endpoint(endpoint) + .credential(new DefaultAzureCredentialBuilder().build()) + .buildClient(); + } + return blobServiceClient; + } +} +``` + +**After:** +```java +public class BlobServiceClientProvider { + private static BlobServiceClient blobServiceClient; + + public static BlobServiceClient getClient() { + if (blobServiceClient == null) { + String endpoint = System.getenv("AZURE_STORAGE_ENDPOINT"); + blobServiceClient = new BlobServiceClientBuilder() + .endpoint(endpoint) + .credential(new DefaultAzureCredentialBuilder().build()) + .buildClient(); + } + return blobServiceClient; + } + + public static BlobServiceClient getClient(String connectionString) { + if (blobServiceClient == null) { + blobServiceClient = new BlobServiceClientBuilder() + .connectionString(testConnectionString) + .buildClient(); + } + return blobServiceClient; + } +} +``` + +### Emulator Connection String Values + +Each emulator's connection string is built from **well-known credentials** that are consistent across all emulator instances. + +| Service | Auth Type | How to Determine the Value | +|---------|-----------|----------------------------| +| Azurite (Blob/Queue/Table) | Connection string | Built from well-known account `devstoreaccount1`, well-known key, and mapped ports (10000/10001/10002). See [Azurite docs](https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite#well-known-storage-account-and-key). | +| Service Bus | Connection string | Built from well-known SAS key + `UseDevelopmentEmulator=true`. See [Service Bus emulator docs](https://learn.microsoft.com/en-us/azure/service-bus-messaging/test-locally-with-service-bus-emulator). | +| Event Hubs | Connection string | Same pattern as Service Bus. See [Event Hubs emulator docs](https://learn.microsoft.com/en-us/azure/event-hubs/test-locally-with-event-hub-emulator). | +| Cosmos DB | Endpoint + key | Built from well-known emulator key + mapped port. See [Cosmos emulator docs](https://learn.microsoft.com/en-us/azure/cosmos-db/emulator). Also requires SSL trust store setup. | + +**Important:** The agent must look up the **actual current default credentials** from the linked official docs at generation time. Emulator defaults may change across versions. Do NOT hardcode connection strings from this file. + +### Modification Rules + +When modifying application code or config to use emulators: + +1. **Minimal changes only** — Only modify what's needed to switch auth from Managed Identity to emulator connection strings. Do not refactor, rename, or restructure. +2. **No new dependencies** — Use only packages already in the project. +3. **Reversible** — Changes should be easily revertable to restore original Managed Identity auth. +4. **No unrelated changes** — Never make refactoring, cleanup, or feature changes alongside auth modifications. + +## Principles + +### Core Principles + +1. **DO integration tests** to verify that different components interact correctly, focusing on data exchange and interface connections. +2. **DO write** comprehensive integration tests using containers to simulate all migrated dependencies, **especially Azure services**. +6. **NEVER modify application source code** to fix TestContainers or Docker compatibility issues. +8. **DO use `*L1Test.java`** as the class name suffix for Layer 1 test classes. This matches Maven Surefire's default `*Test.java` pattern + +### MOST IMPORTANT: Test application code, not the SDK + +Always instantiate and invoke the project's own classes/methods. Never call third-party library APIs directly in the test as a substitute for testing the application. If the application has a service class with methods like `handleRequest()`, `processData()`, or `createBucket()`, your tests MUST call those methods. + **Anti-pattern — DO NOT DO THIS:** + ```java + // WRONG: Test calls SDK directly instead of the application's own class + @Test void testServiceMethod() { + // This tests the SDK, NOT the application code + sdkClient.doOperation("test-input"); + assertTrue(sdkClient.getResult("test-input").exists()); + } + ``` + + **Correct pattern — invoke the application's own entry points:** + ```java + // RIGHT: Test calls the application's own handler method end-to-end + @Test void testHandleRequestProcessesDataEndToEnd() { + // Arrange — pre-populate test data + setupTestData("container1", "data1.txt", "status:ACTIVE\ntimestamp:1573410202"); + + // Act — invoke the APPLICATION's handler, not the SDK + MyHandler handler = new MyHandler(sdkClient); + handler.handleRequest(mockContext); + + // Assert — verify the application produced the expected output + String result = readOutput("output-container", "result.txt"); + assertThat(result).contains("ACTIVE"); + } + ``` + **Never bypass the application class by re-implementing its logic with direct SDK calls.** + + > For Azure Blob Storage-specific examples (Azurite anti-patterns, `BlobServiceClient` injection, `EventHandler` testing), see [azure-storage-testcontainers.md](./azure-storage-testcontainers.md). + +### Make untestable code testable with minimal, safe source changes + +If production code cannot be invoked from a test, introduce the smallest possible change (e.g., adding a method, widening visibility, extracting a parameter) to enable it. Never alter existing behavior, signatures, or control flow. Never work around untestable code by re-implementing its logic in the test. + +### Wire and test the full execution path + +Tests must exercise the end-to-end flow from input through business logic to output, not individual layers (controller layer or database layer) in isolation. + +### Assert on behavior, not structure + +Verify correct values, side effects, and state transitions — not just that a result is non-null or non-empty, nor rely on string containment or format checks alone. **Every write test must read the data back and verify content.** A test whose only assertion is `assertTrue(result.exists())` or `assertNotNull(result)` is effectively testing nothing. + +### Only set up what the test uses + +Every configured property, container, or singleton in setUp must be exercised by the test. Remove dead setup. + +### Test error handling at the application level with precise assertions — this is MANDATORY, not optional. + + **Anti-pattern — DO NOT DO THIS:** + ```java + // WRONG: Only testing happy paths + @Test void testOperation() { sdkClient.doOperation("test"); /* only happy path */ } + ``` + + **Correct pattern:** + ```java + // RIGHT: Test error handling through the application class + @Test void testOperationWithInvalidInput() { + RuntimeException ex = assertThrows(RuntimeException.class, + () -> myService.processInput("INVALID-INPUT")); + assertThat(ex.getMessage()).contains("Failed to process"); + assertThat(ex.getCause()).isInstanceOf(SomeSDKException.class); + } + ``` + + **Correct pattern for JSON deserialization error (common with Redis/cache migrations):** + ```java + // RIGHT: Inject corrupted data directly into the store, then test the app's error path + @Test void testGetPersonWithCorruptedJson() { + // Arrange — write invalid JSON directly to Redis in the SAME namespace the app uses + redisCommands.set("personCache:badKey", "{not-valid-json!!}"); + + // Act & Assert — the app's getPerson() should catch JsonProcessingException and wrap it + RuntimeException ex = assertThrows(RuntimeException.class, + () -> cacheManager.getPerson("badKey")); + assertThat(ex.getMessage()).contains("Failed to deserialize"); + assertThat(ex.getCause()).isInstanceOf(JsonProcessingException.class); + } + ``` + **Note:** When testing deserialization errors, write corrupted data to the **same key namespace/prefix** that the application reads from. A common mistake is writing to namespace `stringCache:key` but reading from `personCache:key` — this just produces a cache miss (null), not a deserialization error. + + > For Azure Blob Storage-specific error handling patterns (e.g., `BlobStorageException` wrapping), see [azure-storage-testcontainers.md](./azure-storage-testcontainers.md#5-azure-blob-specific-test-patterns). + + #### Migration-Critical Error Scenarios Checklist + + The following error scenarios are the most commonly missed in migration integration tests. **You MUST check each row and write a test for every scenario that applies to your migration.** Skipping these is the #1 cause of low error handling scores. + + | # | Scenario | When it applies | What to test | Example assertion | + |---|----------|----------------|--------------|-------------------| + | 1 | **SDK exception wrapping** | App has `catch (SDKException e) { throw new ...}` | Trigger the SDK exception through the app's own method and verify the wrapper exception type, message, AND cause chain | `assertThat(ex.getCause()).isInstanceOf(SDKException.class)` | + | 2 | **Resource-not-found via app method** | App reads blobs, DB rows, cache keys that may not exist | Call the app's read method with a non-existent resource; assert on the specific return (null, empty Optional, 404) — not just "no exception" | `assertThat(dao.load(99999, "x")).isNull()` | + | 3 | **Inconsistent state between stores** | App uses 2+ stores (DB + blob, DB + cache) | Write metadata to one store but NOT the other, then call the app's read method — verify it handles the mismatch (exception, null, graceful fallback) | Insert DB row, don't upload blob -> `dao.load()` should throw/return-null | + | 4 | **Invalid/null input through app methods** | App has public methods accepting user-provided strings, IDs, names | Pass null, empty string, and boundary-length values through the app's own method; assert specific exception type or error response | `assertThrows(IllegalArgumentException.class, () -> svc.create(null))` | + | 5 | **Duplicate/conflict operations** | App creates named resources (containers, keys, DB records) | Create a resource, then try to create it again through the app method; verify the specific conflict behavior (exception type, idempotent success, or error code) | `assertThat(ex.getMessage()).contains("already exists")` | + | 6 | **Delete non-existent resource** | App has delete/remove methods | Call delete on a resource that doesn't exist; verify whether it throws, returns false, or is idempotent — **this often differs between AWS SDK and Azure SDK** | `assertThat(svc.delete("nonexistent")).isFalse()` | + | 7 | **Operations after close/shutdown** | App has lifecycle methods (close, shutdown, stopService) | Call close(), then attempt a normal operation; verify it throws the expected exception (NPE, IllegalStateException, etc.) | `svc.close(); assertThrows(IllegalStateException.class, () -> svc.read("key"))` | + | 8 | **Corrupted/invalid data in store** | App deserializes data from external store (JSON from Redis, parsed blob content) | Write corrupted/malformed data directly to the store, then call the app's read method; verify the deserialization error is properly handled | `redis.set("key", "{bad-json}"); assertThrows(RuntimeException.class, () -> mgr.get("key"))` | + + **Self-check:** Count your error test methods. If you have fewer than 3 error tests from the table above, you almost certainly have gaps. Go back and add more. + +### Cover downstream consumers + +If the migrated code produces output consumed by other components (config generators, report builders), test those consumers too. Verify output is consumable by the next component in the pipeline — write then read back through the real consumer API to confirm round-trip correctness. + +### Only label tests as integration tests if they integrate external systems + +Tests with no containers, network, or filesystem dependencies are unit tests, which is not within the IT scope. Do not generate them alongside integration tests. + +### Stay scoped to the migration target + +Only generate tests for code that uses the migrated service. Do not generate unrelated tests for general project utilities. + +### DO NOT use any test ordering mechanism — this is an AUTOMATIC QUALITY DEDUCTION + +This includes JUnit 5 `@Order` / `@TestMethodOrder`, TestNG `dependsOnMethods` / `priority`, and any other framework-specific ordering. Tests MUST be independent and run in any order. Each test must create its own data and clean up after itself. Test ordering indicates shared mutable state — a critical quality issue. Evaluators specifically check for `@Order` and `@TestMethodOrder` annotations and will deduct points for their presence. + + **Anti-pattern — DO NOT DO THIS:** + ```java + // WRONG: @Order creates test interdependence — automatic quality deduction + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + class MyL1Test { + @Test @Order(1) void testCreate() { ... } + @Test @Order(2) void testRead() { ... } // implicitly depends on testCreate + } + ``` + + **Correct pattern:** + ```java + // RIGHT: Each test is self-contained with its own setup + class MyL1Test { + @BeforeEach void cleanup() { /* clean all state */ } + @Test void testCreate() { /* creates its own data */ } + @Test void testRead() { /* creates its own data, then reads */ } + } + ``` +### DO NOT use reflection to access or modify private fields, and DO NOT widen field visibility (private->protected/public) in production code for test access. + +Both approaches are fragile and tightly couple tests to implementation details. Reflection breaks on Java 17+ with strong encapsulation. Widening visibility pollutes the production API and can cause compilation errors when test classes are in a different package (protected access requires same package or subclass). Instead: + - For singletons: add a package-private or test-visible constructor/factory method to the production class (minimal safe change). + - For environment variables: use `@DynamicPropertySource`, `System.setProperty()` with matching application code reads, constructor/setter injection, or `@TestConfiguration` beans. + - For configuration files: use `@TestPropertySource`, classpath-based overrides in `src/test/resources/`, or `@DynamicPropertySource`. + - For injecting test dependencies: use constructor injection, setter injection, or `@TestConfiguration` with `@Primary` beans. + - For inspecting internal state: add a package-private getter method or test the observable behavior (outputs, side effects) instead of reading internal fields. + +### DO write specific, descriptive assertions + + ```java + // WRONG — poor failure message: "expected: true but was: false" + assertTrue(containers.contains("my-container")); + assertTrue(ex.getCause() instanceof SomeException); + + // RIGHT — clear failure message showing actual values + assertThat(containers).contains("my-container"); + assertThat(ex.getCause()).isInstanceOf(SomeException.class); + assertThat(result.getName()).isEqualTo("expected-name"); // shows both values on failure + ``` + + Regardless of library, always assert on specific values — not just `assertNotNull(result)` or `assertTrue(success)`. Include `assertInstanceOf()` (JUnit 5.8+) instead of `assertTrue(x instanceof Y)` for better error messages. + +### DO structure every test with clear Arrange-Act-Assert (AAA) sections + separated by blank lines. Each test should verify ONE logical behavior. Do not combine multiple unrelated operations in a single test method. + +### DO add `@BeforeEach` cleanup when tests share a container + +If multiple tests use the same TestContainers instance (e.g., a shared Redis or Azurite container), each test must clean up its state in `@BeforeEach` or `@AfterEach` (e.g., flush Redis, delete all blob containers, truncate database tables). Stale data from one test must never affect another. Without cleanup, test execution order determines results — a critical reliability issue. + +### DO verify the test count after `mvn verify` matches expectations + +Before committing, run `mvn verify` and confirm the number of tests discovered and executed matches the number of test methods you wrote. If the count is lower, some tests are not being discovered — fix the build configuration. + +### DO extract shared test infrastructure into a base class or helper when you have 2+ test classes + +If multiple test classes use the same TestContainers setup (e.g., same Azurite/Redis/PostgreSQL container), extract the container declaration, connection string construction, and `@DynamicPropertySource` into a shared abstract base class (e.g., `AbstractAzuriteL1Test` or `AbstractRedisL1Test`). This eliminates duplication and ensures consistent infrastructure configuration. Each test class that needs the container simply extends the base class. + + > For an Azurite-specific shared base class example (`AbstractAzuriteL1Test`), see [azure-storage-testcontainers.md](./azure-storage-testcontainers.md#4-shared-azurite-base-class). + +### DO extract commonly repeated test patterns into helper methods + +If you find yourself writing the same 3+ lines more than twice (e.g., listing blob names, creating a user and getting the ID, uploading test data), extract it into a descriptive helper method. This improves readability and reduces maintenance burden. + +### DO NOT leave `System.out.println` or debug logging statements in test code + +Remove all diagnostic print statements (`System.out.println`, `System.err.println`, `e.printStackTrace()`) before committing. If you need to inspect state during development, use proper assertions instead. Debug output clutters test logs, makes failures harder to diagnose, and indicates incomplete cleanup. + +### NEVER use `Thread.sleep()` to wait for containers to start + +TestContainers' `.start()` already waits for exposed ports to be ready. If you need additional readiness checks, use `.waitingFor(Wait.forListeningPort())` or `.waitingFor(Wait.forLogMessage(...))` on the container definition. `Thread.sleep()` is a flakiness time bomb — too short on slow CI, too long on fast machines. + +**Anti-pattern — DO NOT DO THIS:** +```java +// WRONG: Arbitrary sleep after container start +container.start(); +Thread.sleep(2000); // flaky — may be too short or too long +``` + +**Correct pattern:** +```java +// RIGHT: Use TestContainers built-in wait strategy +static final GenericContainer myContainer = new GenericContainer<>("image:latest") + .withExposedPorts(PORT) + .waitingFor(Wait.forListeningPort()); // Reliable readiness check +``` diff --git a/.github/skills/integration-tests/references/layer2-runner-script-templates.md b/.github/skills/integration-tests/references/layer2-runner-script-templates.md new file mode 100644 index 000000000..76b69ef0a --- /dev/null +++ b/.github/skills/integration-tests/references/layer2-runner-script-templates.md @@ -0,0 +1,287 @@ +# Layer 2 Runner Script Templates + +## Script Role + +The runner script is a **test runner only**. It starts infrastructure, builds the app, runs smoke tests, and reports results. It does NOT manage git commits or handle test failures — that is the agent's responsibility. + +## When to Use + +- **Agent execution (during Layer 2 generation):** After creating the auth commit (Step 4), the agent executes this script to run smoke tests. If tests fail, the agent analyzes errors, applies fixes, and re-runs the script. +- **User execution (manual re-runs):** Users must checkout the auth commit first, then run the script to verify smoke tests still pass. + +## Placeholders to Replace + +When generating the scripts, replace these placeholders with project-specific values: + +- ``: The build command for the detected project type + - Maven: `mvn package -DskipTests` + - Gradle: `./gradlew build -x test` + - .NET: `dotnet build` + - npm: `npm run build` + +- ``: The command to start the application + - Maven (Spring Boot): `mvn spring-boot:run` or `java -jar target/*.jar` + - Gradle (Spring Boot): `./gradlew bootRun` or `java -jar build/libs/*.jar` + - .NET: `dotnet run` or `dotnet MyApp.dll` + - npm: `npm start` + +## Bash Template (run-layer2-tests.sh) + +**NOTE:** This script should be placed in `{modernization-work-folder}/integration-tests/`. The script navigates to the project root to execute commands. + +```bash +#!/bin/bash +set -e + +# --- Auto-generated Layer 2 smoke test runner --- +# Project: {ProjectName} ({Language}, {Framework}) +# Starts infrastructure, builds and runs the app, verifies it stays alive +# and responds to HTTP requests without fatal errors. +# +# Usage: +# bash {modernization-work-folder}/integration-tests/run-layer2-tests.sh +# +# Environment variable overrides (optional): +# APP_PORT - Application port (default: 8080) +# HEALTH_PATH - Health endpoint (default: /) + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" # Navigate to project root +COMPOSE_FILE="$PROJECT_ROOT/src/test/resources/docker-compose.smoke.yml" +APP_LOG="/tmp/app-smoke-$$.log" + +APP_PORT="${APP_PORT:-8080}" +APP_URL="http://localhost:$APP_PORT" +HEALTH_PATH="${HEALTH_PATH:-/health}" # Adjust to detected health endpoint +STARTUP_TIMEOUT=60 +STABILITY_WAIT=5 +CHECKS_PASSED=0 +CHECKS_FAILED=0 +APP_PID="" + +pass() { echo " ✅ $1"; CHECKS_PASSED=$((CHECKS_PASSED + 1)); } +fail() { echo " ❌ $1"; CHECKS_FAILED=$((CHECKS_FAILED + 1)); } + +cleanup() { + [ -n "$APP_PID" ] && kill $APP_PID 2>/dev/null || true + [ -n "$APP_PID" ] && wait $APP_PID 2>/dev/null || true + docker compose -f "$COMPOSE_FILE" down -v 2>/dev/null || true + rm -f "$APP_LOG" +} +trap cleanup EXIT + +cd "$PROJECT_ROOT" + +# --- Step 1: Verify Docker --- +docker info > /dev/null 2>&1 || { echo "ERROR: Docker is not running."; exit 1; } + +# --- Step 2: Start infrastructure --- +echo "Starting infrastructure..." +docker compose -f "$COMPOSE_FILE" up -d --wait + +# --- Step 3: Build --- +echo "Building application..." + + +# --- Step 4: Start application --- +echo "Starting application..." + > "$APP_LOG" 2>&1 & +APP_PID=$! + +# --- Step 5: Check — Process alive --- +sleep $STABILITY_WAIT +if kill -0 $APP_PID 2>/dev/null; then + pass "Process is alive (PID $APP_PID)" +else + fail "Process crashed on startup" + tail -20 "$APP_LOG" 2>/dev/null || true + exit 1 +fi + +# --- Step 6: Check — Health probe --- +HEALTH_STATUS="000" +for i in $(seq 1 $STARTUP_TIMEOUT); do + HEALTH_STATUS=$(curl -sf -o /dev/null -w "%{http_code}" "${APP_URL}${HEALTH_PATH}" 2>/dev/null || echo "000") + [ "$HEALTH_STATUS" != "000" ] && break + kill -0 $APP_PID 2>/dev/null || { fail "Process died during health probe"; break; } + sleep 1 +done +[[ "$HEALTH_STATUS" =~ ^[1234] ]] && pass "Health probe (HTTP $HEALTH_STATUS)" \ + || fail "Health probe failed (HTTP $HEALTH_STATUS)" + +# --- Step 7: Check — Clean startup logs --- +if grep -qiE "FATAL|panic|unhandled.exception|OutOfMemory|StackOverflow|segfault" "$APP_LOG" 2>/dev/null; then + fail "Fatal error patterns in startup logs" +else + pass "No fatal errors in startup logs" +fi + +# --- Summary --- +echo "" +echo "========================================" +echo "Results: $CHECKS_PASSED passed, $CHECKS_FAILED failed" +if [ $CHECKS_FAILED -gt 0 ]; then + echo "❌ Layer 2 smoke tests FAILED" + exit 1 +fi +echo "✅ Layer 2 smoke tests PASSED" +echo "========================================" +``` + +## PowerShell Template (run-layer2-tests.ps1) + +**NOTE:** This script should be placed in `{modernization-work-folder}/integration-tests/`. The script navigates to the project root to execute commands. + +```powershell +#Requires -Version 5.1 +$ErrorActionPreference = "Stop" + +# --- Auto-generated Layer 2 smoke test runner --- +# Project: {ProjectName} ({Language}, {Framework}) +# Starts infrastructure, builds and runs the app, verifies it stays alive +# and responds to HTTP requests without fatal errors. +# +# Usage: +# powershell {modernization-work-folder}/integration-tests/run-layer2-tests.ps1 +# +# Environment variable overrides (optional): +# APP_PORT - Application port (default: 8080) +# HEALTH_PATH - Health endpoint (default: /) + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$ProjectRoot = Resolve-Path (Join-Path $ScriptDir "..\..") # Navigate to project root +$ComposeFile = Join-Path $ProjectRoot "src\test\resources\docker-compose.smoke.yml" +$AppLog = Join-Path $env:TEMP "app-smoke-$PID.log" + +$AppPort = if ($env:APP_PORT) { $env:APP_PORT } else { 8080 } +$AppUrl = "http://localhost:$AppPort" +$HealthPath = if ($env:HEALTH_PATH) { $env:HEALTH_PATH } else { "/health" } +$StartupTimeout = 60 +$StabilityWait = 5 +$ChecksPassed = 0 +$ChecksFailed = 0 +$AppProcess = $null + +function Pass($message) { + Write-Host " ✅ $message" -ForegroundColor Green + $script:ChecksPassed++ +} + +function Fail($message) { + Write-Host " ❌ $message" -ForegroundColor Red + $script:ChecksFailed++ +} + +function Cleanup { + if ($AppProcess -and !$AppProcess.HasExited) { + Stop-Process -Id $AppProcess.Id -Force -ErrorAction SilentlyContinue + Wait-Process -Id $AppProcess.Id -ErrorAction SilentlyContinue + } + docker compose -f $ComposeFile down -v 2>$null + if (Test-Path $AppLog) { Remove-Item $AppLog -Force } +} + +# Register cleanup +try { + Set-Location $ProjectRoot + + # --- Step 1: Verify Docker --- + docker info >$null 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Host "ERROR: Docker is not running." -ForegroundColor Red + exit 1 + } + + # --- Step 2: Start infrastructure --- + Write-Host "Starting infrastructure..." + docker compose -f $ComposeFile up -d --wait + + # --- Step 3: Build --- + Write-Host "Building application..." + + + # --- Step 4: Start application --- + Write-Host "Starting application..." + $AppProcess = Start-Process -FilePath -RedirectStandardOutput $AppLog -RedirectStandardError $AppLog -PassThru -NoNewWindow + + # --- Step 5: Check — Process alive --- + Start-Sleep -Seconds $StabilityWait + if (!$AppProcess.HasExited) { + Pass "Process is alive (PID $($AppProcess.Id))" + } else { + Fail "Process crashed on startup" + if (Test-Path $AppLog) { Get-Content $AppLog -Tail 20 } + exit 1 + } + + # --- Step 6: Check — Health probe --- + $HealthStatus = "000" + for ($i = 1; $i -le $StartupTimeout; $i++) { + try { + $response = Invoke-WebRequest -Uri "$AppUrl$HealthPath" -UseBasicParsing -TimeoutSec 1 -ErrorAction SilentlyContinue + $HealthStatus = $response.StatusCode + break + } catch { + $HealthStatus = "000" + } + if ($AppProcess.HasExited) { + Fail "Process died during health probe" + break + } + Start-Sleep -Seconds 1 + } + if ($HealthStatus -match "^[1234]") { + Pass "Health probe (HTTP $HealthStatus)" + } else { + Fail "Health probe failed (HTTP $HealthStatus)" + } + + # --- Step 7: Check — Clean startup logs --- + if (Test-Path $AppLog) { + $logContent = Get-Content $AppLog -Raw + if ($logContent -match "(?i)(FATAL|panic|unhandled.?exception|OutOfMemory|StackOverflow|segfault)") { + Fail "Fatal error patterns in startup logs" + } else { + Pass "No fatal errors in startup logs" + } + } + + # --- Summary --- + Write-Host "" + Write-Host "========================================" + Write-Host "Results: $ChecksPassed passed, $ChecksFailed failed" + if ($ChecksFailed -gt 0) { + Write-Host "❌ Layer 2 smoke tests FAILED" -ForegroundColor Red + exit 1 + } + Write-Host "✅ Layer 2 smoke tests PASSED" -ForegroundColor Green + Write-Host "========================================" +} finally { + Cleanup +} +``` + +## Script Behavior + +### What the Script Does + +1. **Verify Docker** — Check if Docker is running +2. **Start infrastructure** — `docker compose up` with all dependencies +3. **Build application** — Run project-specific build command +4. **Start application** — Run in background, log to temp file +5. **Check: Process alive** — Verify process didn't crash (5s stability wait) +6. **Check: Health probe** — HTTP health check (60s timeout, any non-5xx = pass) +7. **Check: Clean logs** — Scan for fatal error patterns (FATAL, panic, OOM, etc.) +8. **Report results** — Print pass/fail summary +9. **Cleanup** — Kill app, docker down, remove temp log + +### Exit Codes + +- `0` — All checks passed +- `1` — One or more checks failed + +### Environment Variables (Optional) + +Users can override defaults: +- `APP_PORT` — Application port (default: 8080) +- `HEALTH_PATH` — Health endpoint path (default: /health) diff --git a/.github/skills/integration-tests/references/layer2-smoke-tests.md b/.github/skills/integration-tests/references/layer2-smoke-tests.md new file mode 100644 index 000000000..f85215ee1 --- /dev/null +++ b/.github/skills/integration-tests/references/layer2-smoke-tests.md @@ -0,0 +1,261 @@ +# Layer 2: Smoke Tests + +**Goal:** "Does the whole application start and stay alive?" + +Verify the complete application boots successfully, reaches a healthy state, and produces no crash-level errors. Layer 2 does **NOT** test individual features, APIs, or business logic — that is Layer 1's responsibility. + +## What Layer 2 Is and Is Not + +| Layer 2 IS | Layer 2 IS NOT | +|------------|----------------| +| Verifying the process starts without crashing | Testing specific API endpoints or features | +| Checking a single health/readiness probe | Hitting every REST route or gRPC method | +| Scanning startup logs for fatal errors | Asserting on response payloads or data | +| Detecting missing configuration or broken DI | Exercising SDK integrations or components | +| Provisioning ALL infrastructure the app needs | Only setting up migrated-component containers | + +## No Test Classes Required + +Layer 2 does **NOT** generate test classes. The runner script (`run-layer2-tests.sh` / `run-layer2-tests.ps1`) **is** the test — it provisions infrastructure, starts the app, runs health checks via `curl` and shell built-ins, and reports pass/fail. No JUnit test code. + +**Layer 2 is independent of Layer 1.** Layer 1 tests only migrated components; Layer 2 must provision ALL application dependencies. + +## Prerequisites + +- Application builds successfully +- Docker installed and running + +## Azure Authentication for Smoke Tests + +Migrated applications typically use **Managed Identity** (`DefaultAzureCredential`) which only works on Azure infrastructure. For local smoke testing, you must modify the application to connect to local emulators instead. + +**See [azure-auth-strategies.md](./azure-auth-strategies.md) for:** +- How to classify Azure dependencies (Emulatable, Lazy, Non-emulatable) +- Emulator images and configuration +- Authentication modification strategies (config-driven vs code-driven apps) +- Emulator connection string values +- Decision flow for handling each dependency type + +**Summary:** For each Azure dependency, determine if it's emulatable (use local emulator), lazy (skip), or startup-required non-emulatable (disable via config or recommend Layer 3). + +## Workflow + +> **CRITICAL — Commit order matters.** Follow Steps 1–8 in exact order. The artifacts commit (Step 3) MUST come before the auth commit (Step 4). Do not combine them. Do not reorder them. + +The workflow produces **a multi-commit sequence** (exactly 3 commits): + +| # | Commit | Contains | +|---|--------|----------| +| 1 | `[layer-2] Add smoke test artifacts` | docker-compose.smoke.yml, runner scripts, AND any source code fixes needed for smoke tests to pass. **All fixes (infrastructure, runner scripts, source code) must be amended to this commit.** | +| 2 | `[smoke-test] Replace Managed Identity with emulator connection strings` | Config file changes (e.g., `application.properties`) with explicit emulator connection strings, and any necessary auth-related code changes. **Auth-related fixes must be amended to this commit.** | +| 3 | `[smoke-test] Restore Managed Identity auth` | Reverts **only commit 2** by SHA — restores original Managed Identity config | + +**CRITICAL — Amend strategy:** +- Fixes to docker-compose.smoke.yml → amend commit 1 +- Fixes to runner scripts → amend commit 1 +- Source code bugs discovered during testing (DI wiring, null refs, etc.) → **amend commit 1** +- Fixes to auth config files → amend commit 2 +- Fixes to auth-related code → amend commit 2 + +This ensures when users checkout commit 2 (auth) to run smoke tests locally, they have ALL fixes: commit 1 contains working artifacts AND all source code fixes, commit 2 contains working auth config. + +The agent executes the smoke test runner script (Step 7) after creating commit 2, and may need to amend commits 1 or 2 during retries. + +**Retry on failure:** If the runner script fails, the agent analyzes the output and attempts a fix: +- **Smoke-artifact failure** (wrong port in compose, bad docker-compose config, bad runner script) → **amend commit 1** with fixes and re-run Step 7 +- **Source-code failure** (broken DI wiring, null reference in initializer, missing config guard, startup crash from migrated code) → **amend commit 1** with fixes and re-run Step 7. Apply the **Handling Test Failures** guidance from the main skill file. +- **Auth-config failure** (wrong connection string in config file, missing config property, auth-related code issue) → **amend commit 2** with fixes and re-run Step 7 + +Max **3 retries** total (across all failure types). If the agent cannot diagnose the root cause or the fix would require architectural changes, stop and report. + +### Step 1: Analyze ALL Application Dependencies + +Scan the **entire** application to identify every external dependency required for startup: + +- Build files: `pom.xml`, `build.gradle`, `*.csproj`, `package.json` +- Config: `application.yml`, `application.properties`, `appsettings.json`, `.env` +- Source code: client initializations, `@Autowired`/`@Inject` services +- Existing Docker/compose files + +**For each Azure dependency, classify it** using the decision flow from [azure-auth-strategies.md](./azure-auth-strategies.md): +- **Emulatable** → Add emulator to docker-compose, will modify auth in Step 4 +- **Lazy** → Skip (doesn't block startup) +- **Non-emulatable** → Disable via config in Step 4, or recommend Layer 3 + +**For non-Azure dependencies:** + +| Category | Example | Action | +|----------|---------|--------| +| Standard databases | PostgreSQL, MySQL, MongoDB, Redis | Add to `docker-compose.smoke.yml` with standard image | +| Azure wire-compatible | Azure DB for PostgreSQL/MySQL, Azure Cache for Redis, Azure SQL | Use open-source image (`postgres:16`, `redis:7`, `mcr.microsoft.com/mssql/server`) | + +**Rules:** Include anything that crashes the app on startup. Skip lazy/on-demand dependencies. Reuse Docker images and ports from Layer 1 where applicable. If an existing `docker-compose.yml` already defines all needed services, prefer reusing it. + +### Step 2: Generate Docker Compose + +Create `docker-compose.smoke.yml` in the test resources directory. The file provisions ALL required dependencies. Every service must have a healthcheck. Include proper ports and volume mounts. + +**File location:** `src/test/resources/docker-compose.smoke.yml` + +### Step 3: Generate Runner Scripts and Commit Artifacts + +Generate `run-layer2-tests.sh` and `run-layer2-tests.ps1` (see Runner Script Template below). + +**File locations:** +- `{modernization-work-folder}/integration-tests/run-layer2-tests.sh` +- `{modernization-work-folder}/integration-tests/run-layer2-tests.ps1` + +Then commit all generated artifacts: + +``` +git add -A +git commit -m "[layer-2] Add smoke test artifacts" +``` + +This commit initially contains only generated files (docker-compose.smoke.yml, runner scripts). No source code changes yet. Files in `.github/` may be excluded if the project's `.gitignore` blocks them — this is acceptable. + +**CRITICAL:** If smoke tests fail during Step 7, amend this commit with ALL fixes: +``` +# After fixing docker-compose.smoke.yml, runner scripts, OR source code +git add -A +git commit --amend --no-edit +``` + +This includes source code bugs discovered during smoke testing. The commit message says "Add smoke test artifacts" but the commit will contain test infrastructure (docker-compose, runner scripts) AND source code fixes needed for tests to pass. This ensures users who checkout the auth commit (Step 4) will have all fixes via this commit in git history. + +### Step 4: Commit Auth Changes + +**CRITICAL:** This step is MANDATORY. You must ALWAYS create an auth commit that modifies config files. + +Modify the application configuration files (NOT environment variables) to use explicit emulator connection strings. See [azure-auth-strategies.md](./azure-auth-strategies.md) for: +- Which config files to modify (`application.properties`, `appsettings.json`, etc.) +- Emulator connection string formats +- Code modification patterns if needed + +Create the auth commit: + +```bash +git add -A +git commit -m "[smoke-test] Replace Managed Identity with emulator connection strings" +``` + +Record the commit SHA for Step 8. + +### Step 5: Verify Config File Changes + +**Do NOT modify runner scripts with environment variables.** The config files from Step 4 contain all necessary connection strings. + +Verify that the auth commit includes: +- All modified config files with explicit connection string values +- Any code changes if the app was hardcoded/plain application +- No environment variable exports or overrides + +The runner scripts will start the application as-is, and the app will read connection strings from the config files you just modified. + +### Step 6: Auto-Detect Application Type + +Analyze the project to determine how it starts and how to verify liveness: + +| Indicator | App Type | Start Command | Liveness Check | +|-----------|----------|---------------|----------------| +| Spring Boot, `@SpringBootApplication` | Java Web/API | `./mvnw spring-boot:run` or `java -jar` | HTTP health probe | +| `@Scheduled`, background jobs | Worker/Background | `./mvnw spring-boot:run` | Process stays alive for N seconds | +| `main()` with no server | CLI/Batch | `java -jar` | Process exits with code 0 | + +### Step 7: Execute Smoke Tests via Runner Script + +After creating the auth commit (Step 4) and detecting the application type (Step 6), execute the smoke test runner script: + +```bash +bash {modernization-work-folder}/integration-tests/run-layer2-tests.sh +# OR on Windows: +powershell {modernization-work-folder}/integration-tests/run-layer2-tests.ps1 +``` + +**The script will:** +1. Verify Docker is running +2. Start infrastructure (`docker compose up`) +3. Build the application +4. Start the application in background +5. Run three health checks: + - **Check 1:** Process is alive after stability wait (5s) + - **Check 2:** Health probe returns non-5xx HTTP response (web/API apps only) + - **Check 3:** No fatal error patterns in startup logs (FATAL, panic, OutOfMemory, etc.) +6. Report pass/fail and exit with appropriate code +7. Clean up (kill app, docker down) + +**If the script exits with code 0:** +- ✅ Tests passed +- Proceed to Step 8 (create restore commit) + +**If the script exits with non-zero:** +- ❌ Tests failed (likely auth-config issues) +- Analyze the script's output to determine root cause +- **Amend the auth commit (Step 5)** with fixes +- Re-run the script (max 3 retries total) + +**Important:** At this point, all source code fixes should already be done (Step 4). Failures here are typically auth configuration issues (wrong connection strings, missing config properties). + +**CRITICAL — Do NOT Create Test Classes:** +- Layer 2 does NOT use JUnit test classes +- The runner script IS the test - it uses shell commands (curl, grep, etc.) +- Do NOT create any `.java` test files for Layer 2 +- If you need to improve smoke testing, modify the runner scripts, not create test classes + +### Step 8: Create Restore Commit + +After smoke tests pass, create the restore commit to restore Managed Identity auth: + +```bash +git revert --no-edit +git commit --amend -m "[smoke-test] Restore Managed Identity auth" +``` + +This keeps the auth commit in history (visible in `git log`) while restoring the codebase to use Managed Identity. + +**CRITICAL:** All smoke test artifacts (docker-compose.smoke.yml, runner scripts) and source code fixes are in commit 1. Users can checkout the auth commit (commit 2) to run smoke tests locally and will have all necessary fixes via commit 1 in git history. + +## Handling Failures + +**CRITICAL:** All fixes must be amended to either commit 1 (smoke artifacts + source code) or commit 2 (auth config). Never create separate fix commits. + +Testing happens in Step 7 (after both commits 1 and 2 are created). When failures occur: + +| Failure | Likely Cause | Action | +|---------|-------------|--------| +| Infrastructure won't start | Port conflict, Docker issue, bad docker-compose config | **Amend commit 1** (fix docker-compose.smoke.yml) and re-run Step 7 | +| Build fails | Compile error in source code, missing dependencies | **Amend commit 1** (fix source code) and re-run Step 7. Apply Handling Test Failures guidance. | +| Process crashes immediately | Broken DI wiring, null ref, missing config guard, startup crash, OR wrong auth config | **Analyze stack trace** → amend commit 1 (source code bugs) or commit 2 (auth issues) and re-run Step 7 | +| Runner script fails | Wrong app start command, incorrect paths, bad classpath | **Amend commit 1** (fix runner scripts) and re-run Step 7 | +| Health probe times out | Wrong port in config, slow startup | **Amend commit 1** (fix runner script health check) and re-run Step 7 | +| Health probe returns 5xx | Internal error from missing config or code bug | **Analyze** → amend commit 1 (code) or commit 2 (auth) and re-run Step 7 | +| Fatal log patterns | Unhandled exceptions at startup | **Analyze stack trace** → amend commit 1 (source code) or commit 2 (auth config) if root cause is identifiable; otherwise report | +| Fatal log patterns | Unhandled exceptions at startup | **Analyze stack trace → fix** if root cause is identifiable; otherwise report | + +**Key principles:** +- Layer 2 fixes **both** its own generated artifacts **and** source-code issues it discovers. The goal is a passing smoke test, not just a report. +- Use the **Handling Test Failures** guidance from the main skill file to decide what to fix and how. +- Keep source-code fixes **minimal and targeted** — fix the specific startup blocker (e.g., null guard, missing config binding, broken DI registration). Do not refactor or change architecture. +- Max **3 retries** total. If the root cause is unclear, the fix would require architectural changes, or the app genuinely needs a real Azure service with no workaround, stop and report. + +## Standardized Runner Scripts + +Generate `run-layer2-tests.sh` and `run-layer2-tests.ps1` in `{modernization-work-folder}/integration-tests/`. These are test runners that start infrastructure, build the app, run smoke tests, and report results. + +**See [layer2-runner-script-templates.md](./layer2-runner-script-templates.md) for:** +- Complete bash and PowerShell script templates +- Placeholder replacement guide +- Script behavior and exit codes +- Environment variable overrides + +Users run: +- Unix: `bash {modernization-work-folder}/integration-tests/run-layer2-tests.sh` +- Windows: `powershell {modernization-work-folder}/integration-tests/run-layer2-tests.ps1` + +## Pass Criteria + +- All infrastructure containers reach healthy state +- Application builds and starts without crashing +- Health probe returns non-5xx (web/API apps) +- No fatal-level errors in startup logs +- Clean shutdown of app and infrastructure diff --git a/.github/skills/integration-tests/references/layer3-azure-integration.md b/.github/skills/integration-tests/references/layer3-azure-integration.md new file mode 100644 index 000000000..df30130ef --- /dev/null +++ b/.github/skills/integration-tests/references/layer3-azure-integration.md @@ -0,0 +1,194 @@ +# Layer 3: Azure Integration Tests + +**Goal:** "Does it work in real cloud environment?" + +Deploy to Azure staging environment and test against real Azure services. + +## Test Isolation + +- **DO use `L3Test` as the class name suffix** for all Layer 3 test classes (e.g., `AzureSqlL3Test`, `BlobStorageL3Test`). The `*Test` suffix matches Maven Surefire's default discovery pattern — no build plugin changes needed. +- **DO annotate every test class with a `Layer3` tag/category** so runner scripts filter precisely and never trigger Layer 1, 2, or 4 tests. See the Test Isolation Convention in the main skill file for the exact annotation per language/framework. +- The runner script **MUST filter by the `Layer3` tag** (e.g., `mvn verify -Dgroups=Layer3`, `dotnet test --filter Category=Layer3`, `pytest -m layer3`) + +## Prerequisites + +- Azure subscription with staging environment +- Azure CLI installed and authenticated +- Infrastructure-as-Code templates (Bicep/ARM/Terraform) + +## Workflow + +### 1. Identify Azure Services + +Map application dependencies to Azure services: + +| Local Dependency | Azure Service | Configuration Needed | +|-----------------|---------------|---------------------| +| SQL Server | Azure SQL Database | Connection string, firewall | +| PostgreSQL | Azure Database for PostgreSQL | Connection string, SSL | +| Redis | Azure Cache for Redis | Connection string, access key | +| File storage | Azure Blob Storage | Connection string, container | +| Message queue | Azure Service Bus | Connection string, queue | +| Key vault | Azure Key Vault | Managed identity, access policy | +| Logging | Application Insights | Instrumentation key | + +### 2. Deploy to Staging + +#### Using Azure CLI + +```bash +# Set variables +RESOURCE_GROUP="rg-app-staging" +LOCATION="eastus" +APP_NAME="app-integration-test-$(date +%s)" + +# Create resource group +az group create --name $RESOURCE_GROUP --location $LOCATION + +# Deploy infrastructure +az deployment group create \ + --resource-group $RESOURCE_GROUP \ + --template-file infra/main.bicep \ + --parameters environment=staging + +# Deploy application +az webapp deploy \ + --resource-group $RESOURCE_GROUP \ + --name $APP_NAME \ + --src-path ./publish.zip +``` + +#### Using GitHub Actions + +```yaml +- name: Deploy to Staging + uses: azure/webapps-deploy@v2 + with: + app-name: ${{ env.APP_NAME }} + slot-name: staging + package: ./publish.zip +``` + +### 3. Configure Azure Services + +```bash +# Get connection strings from deployed resources +SQL_CONN=$(az sql db show-connection-string \ + --server $SQL_SERVER \ + --name $DB_NAME \ + --client ado.net) + +REDIS_CONN=$(az redis list-keys \ + --resource-group $RESOURCE_GROUP \ + --name $REDIS_NAME \ + --query primaryKey -o tsv) + +STORAGE_CONN=$(az storage account show-connection-string \ + --resource-group $RESOURCE_GROUP \ + --name $STORAGE_NAME \ + --query connectionString -o tsv) + +# Update app settings +az webapp config appsettings set \ + --resource-group $RESOURCE_GROUP \ + --name $APP_NAME \ + --settings \ + "ConnectionStrings__Default=$SQL_CONN" \ + "Redis__ConnectionString=$REDIS_CONN" \ + "Storage__ConnectionString=$STORAGE_CONN" +``` + +### 4. Run Integration Tests Against Staging + +```bash +# Set test target to staging URL +export TEST_BASE_URL="https://$APP_NAME.azurewebsites.net" + +# Run Layer 3 integration tests only (filter by Layer3 tag) +dotnet test --filter Category=Layer3 +``` + +Test scenario areas for Azure (all tagged with `Layer3`): + +| Scenario Area | Tests | +|----------|-------| +| Database | CRUD operations, transactions, stored procedures | +| Storage | Blob upload/download, container operations | +| Cache | Get/set, expiration, distributed locks | +| Messaging | Send/receive, dead letter, retry | +| Identity | Authentication, authorization, managed identity | + +### 5. Validate Azure-Specific Features + +```csharp +[Trait("Category", "Layer3")] +public class AzureSqlL3Test +{ + [Fact] + public async Task CanConnectToAzureSql() + { + await using var conn = new SqlConnection(_connectionString); + await conn.OpenAsync(); + conn.State.Should().Be(ConnectionState.Open); + } + + [Fact] + public async Task TransactionWorksAcrossMultipleTables() + { + // Test Azure SQL transaction behavior + } +} + +[Trait("Category", "Layer3")] +public class AzureBlobL3Test +{ + [Fact] + public async Task CanUploadAndDownloadBlob() + { + var container = _blobClient.GetBlobContainerClient("test"); + await container.CreateIfNotExistsAsync(); + + var blob = container.GetBlobClient("test.txt"); + await blob.UploadAsync(new BinaryData("test content")); + + var downloaded = await blob.DownloadContentAsync(); + downloaded.Value.Content.ToString().Should().Be("test content"); + } +} +``` + +### 6. Collect Metrics and Logs + +```bash +# Get Application Insights logs +az monitor app-insights query \ + --app $APP_INSIGHTS_NAME \ + --analytics-query "traces | where timestamp > ago(1h) | order by timestamp desc | take 100" + +# Check for errors +az monitor app-insights query \ + --app $APP_INSIGHTS_NAME \ + --analytics-query "exceptions | where timestamp > ago(1h)" + +# Get performance metrics +az monitor metrics list \ + --resource $APP_RESOURCE_ID \ + --metric "Requests,AverageResponseTime,Http5xx" +``` + +### 7. Cleanup + +```bash +# Delete staging resources +az group delete --name $RESOURCE_GROUP --yes --no-wait +``` + +## Pass Criteria + +- Application deploys successfully to Azure +- All Azure service connections work +- Integration tests pass against real services +- No 5xx errors in Application Insights +- Performance is within acceptable thresholds +- Logs show no critical errors +- Runner scripts generated per the Standardized Runner Scripts convention in the main skill file diff --git a/.github/skills/integration-tests/references/layer4-behavioral-comparison.md b/.github/skills/integration-tests/references/layer4-behavioral-comparison.md new file mode 100644 index 000000000..0eceb1c71 --- /dev/null +++ b/.github/skills/integration-tests/references/layer4-behavioral-comparison.md @@ -0,0 +1,242 @@ +# Layer 4: Behavioral Comparison + +**Goal:** "Does it match the original?" + +Run old and new versions side-by-side and compare outputs for identical inputs. + +## Test Isolation + +- **DO use `L4Test` as the class name suffix** for all Layer 4 behavioral comparison test classes (e.g., `OrderApiL4Test`, `UserServiceL4Test`). The `*Test` suffix matches Maven Surefire's default discovery pattern — no build plugin changes needed. +- **DO annotate every test class with a `Layer4` tag/category** so runner scripts filter precisely and never trigger Layer 1, 2, or 3 tests. See the Test Isolation Convention in the main skill file for the exact annotation per language/framework. +- The runner script **MUST filter by the `Layer4` tag** (e.g., `mvn verify -Dgroups=Layer4`, `dotnet test --filter Category=Layer4`, `pytest -m layer4`) + +## Prerequisites + +- Access to both original and migrated application code +- Ability to run both versions simultaneously +- Test data/scenarios from production or comprehensive test suite + +## Workflow + +### 1. Set Up Side-by-Side Environment + +``` +┌─────────────────┐ ┌─────────────────┐ +│ Original App │ │ Migrated App │ +│ (Port 8080) │ │ (Port 8081) │ +└────────┬────────┘ └────────┬────────┘ + │ │ + └───────────┬───────────┘ + │ + ┌──────┴──────┐ + │ Comparator │ + │ Tests │ + └─────────────┘ +``` + +#### Docker Compose Setup + +```yaml +version: '3.8' +services: + original-app: + build: + context: ./original + ports: + - "8080:80" + environment: + - ConnectionStrings__Default=${DB_CONNECTION} + + migrated-app: + build: + context: ./migrated + ports: + - "8081:80" + environment: + - ConnectionStrings__Default=${DB_CONNECTION} + + # Shared database for consistent state + database: + image: mcr.microsoft.com/mssql/server:2022-latest + environment: + - ACCEPT_EULA=Y + - SA_PASSWORD=YourStrong!Passw0rd +``` + +### 2. Define Comparison Test Cases + +Identify all testable behaviors: + +| Category | Test Inputs | Comparison Points | +|----------|-------------|-------------------| +| API responses | Same HTTP requests | Status code, response body, headers | +| Data operations | Same CRUD operations | Database state, return values | +| Business logic | Same input parameters | Calculation results, decisions | +| Error handling | Invalid inputs | Error codes, messages | +| Edge cases | Boundary values | Behavior consistency | + +### 3. Create Comparison Test Framework + +```csharp +public class BehaviorComparisonTests +{ + private readonly HttpClient _originalClient; + private readonly HttpClient _migratedClient; + + public BehaviorComparisonTests() + { + _originalClient = new HttpClient { BaseAddress = new Uri("http://localhost:8080") }; + _migratedClient = new HttpClient { BaseAddress = new Uri("http://localhost:8081") }; + } + + [Theory] + [MemberData(nameof(GetTestCases))] + public async Task ResponsesMatch(TestCase testCase) + { + // Execute same request against both + var originalResponse = await ExecuteRequest(_originalClient, testCase); + var migratedResponse = await ExecuteRequest(_migratedClient, testCase); + + // Compare responses + var comparison = CompareResponses(originalResponse, migratedResponse); + + comparison.IsMatch.Should().BeTrue( + $"Mismatch in {testCase.Name}:\n{comparison.Differences}"); + } + + private ComparisonResult CompareResponses(Response original, Response migrated) + { + var differences = new List(); + + // Compare status codes + if (original.StatusCode != migrated.StatusCode) + differences.Add($"Status: {original.StatusCode} vs {migrated.StatusCode}"); + + // Compare response bodies (with normalization) + var normalizedOriginal = NormalizeResponse(original.Body); + var normalizedMigrated = NormalizeResponse(migrated.Body); + + if (!JsonEquals(normalizedOriginal, normalizedMigrated)) + differences.Add($"Body differs:\nOriginal: {normalizedOriginal}\nMigrated: {normalizedMigrated}"); + + return new ComparisonResult + { + IsMatch = differences.Count == 0, + Differences = string.Join("\n", differences) + }; + } + + private string NormalizeResponse(string json) + { + // Remove fields that are expected to differ + var obj = JsonSerializer.Deserialize(json); + return RemoveIgnoredFields(obj, new[] { "timestamp", "requestId", "version" }); + } +} +``` + +### 4. Handle Expected Differences + +Some differences are expected and should be ignored: + +| Field Type | Example | Handling | +|-----------|---------|----------| +| Timestamps | `createdAt`, `updatedAt` | Ignore or compare format only | +| IDs | `requestId`, `correlationId` | Ignore | +| Version info | `apiVersion`, `buildNumber` | Ignore | +| Order | Array element order | Sort before compare | +| Precision | Float/decimal precision | Round to acceptable precision | + +```csharp +public class ResponseNormalizer +{ + private readonly HashSet _ignoredFields = new() + { + "timestamp", "createdAt", "updatedAt", + "requestId", "correlationId", "traceId", + "version", "buildNumber" + }; + + public JsonElement Normalize(JsonElement element) + { + // Remove ignored fields + // Sort arrays + // Normalize number precision + // etc. + } +} +``` + +### 5. Generate Comparison Report + +```markdown +# Behavioral Comparison Report + +## Summary +- Total test cases: 150 +- Matching: 147 (98%) +- Differences: 3 (2%) + +## Differences Found + +### Case: GET /api/orders/123 +| Field | Original | Migrated | +|-------|----------|----------| +| `items[0].price` | 19.99 | 19.990000 | + +**Analysis:** Floating point precision difference, acceptable. + +### Case: POST /api/users (duplicate email) +| Aspect | Original | Migrated | +|--------|----------|----------| +| Status | 400 | 409 | +| Message | "Email exists" | "Duplicate email" | + +**Analysis:** Different but semantically equivalent error handling. + +## Recommendations +1. Accept precision differences as expected +2. Review error message changes with stakeholders +3. All critical business logic matches ✓ +``` + +### 6. Run Comparison Suite + +```bash +# Start both applications +docker-compose up -d + +# Wait for both to be healthy +./wait-for-healthy.sh http://localhost:8080/health +./wait-for-healthy.sh http://localhost:8081/health + +# Run Layer 4 comparison tests only (filter by Layer4 tag) +dotnet test --filter Category=Layer4 + +# Generate report +dotnet test --logger "html;LogFileName=comparison-report.html" + +# Cleanup +docker-compose down +``` + +## Handling Database State + +For stateful comparisons, ensure both applications start with identical data: + +```bash +# Reset database before each test +docker-compose exec database /opt/mssql-tools/bin/sqlcmd \ + -S localhost -U sa -P 'YourStrong!Passw0rd' \ + -i /scripts/reset-test-data.sql +``` + +## Pass Criteria + +- All API responses match (after normalization) +- Database state changes are identical +- Business logic produces same results +- Error handling is semantically equivalent +- Performance is comparable (within 20% variance) +- No regressions in functionality +- Runner scripts generated per the Standardized Runner Scripts convention in the main skill file diff --git a/.github/skills/rearchitect/SKILL.md b/.github/skills/rearchitect/SKILL.md new file mode 100644 index 000000000..7a94508ec --- /dev/null +++ b/.github/skills/rearchitect/SKILL.md @@ -0,0 +1,126 @@ +--- +name: rearchitect +description: Scan project for Apache Struts and WinForms usage, report findings with modern alternatives +--- + +# Rearchitect — Legacy Framework Detection + +Scan the current project to detect outdated or unmaintained frameworks/technology stacks, report findings, and suggest modern alternatives. Save results via the `write_assessment_result` tool. + +## Input Parameters + +- `workspace-path` (optional): Path to the project to analyze (defaults to current directory) + +## Execution Steps + +### Step 1: Determine Project Type + +Identify the project's technology stack by looking for marker files: + +| Marker File | Project Type | +|-------------|-------------| +| `pom.xml`, `build.gradle`, `build.gradle.kts` | Java/JVM | +| `*.csproj`, `*.sln`, `*.slnx` | .NET | +| `web.xml` | Java Web (Servlet) | + +### Step 2: Run Detection + +**Only** check the two targets listed below. Do **not** scan for or report any other frameworks, libraries, or dependencies beyond these two targets. + +--- + +#### Required Targets + +##### 1. Apache Struts + +**Configuration file detection:** +- `pom.xml` / `build.gradle` / `build.gradle.kts`: search for `org.apache.struts`, `struts2-core`, `struts-core`, `struts-taglib` +- `web.xml`: search for `org.apache.struts`, `StrutsPrepareAndExecuteFilter`, `ActionServlet` + +**Source code detection:** +- Java files: search for `import org.apache.struts`, `import com.opensymphony.xwork2` +- JSP files: search for `<%@ taglib.*struts`, ` Apache Struts has reached end-of-life and no longer receives security patches or bug fixes. It has a history of critical remote code execution vulnerabilities (e.g., CVE-2017-5638, CVE-2023-50164) that made it one of the most exploited frameworks in the Java ecosystem. Continued use exposes the application to known, unpatched attack vectors. Migration to a modern, actively maintained framework such as Spring Boot is strongly recommended to ensure ongoing security support and access to current Java platform features. + +##### 2. WinForms (Windows Forms) + +**Configuration file detection:** +- `.csproj`: search for `true`, `System.Windows.Forms`, `WinExe` (combined with WindowsForms references) + +**Source code detection:** +- C# files: search for `using System.Windows.Forms`, `: Form`, `: UserControl` (in System.Windows.Forms context) +- Presence of `.Designer.cs` files + +**Alternatives:** WPF, MAUI, Avalonia UI, or Blazor Desktop + +**Fixed explanation (use verbatim when detected):** +> Windows Forms (WinForms) is a legacy UI framework tied exclusively to Windows and .NET Framework. While it still receives minimal maintenance in .NET 8+, it lacks modern UI capabilities such as responsive layouts, high-DPI scaling, hardware-accelerated rendering, and cross-platform support. Its designer-centric, event-driven programming model makes it difficult to adopt modern patterns like MVVM or data binding. Migrating to WPF is recommended for richer user experiences, better maintainability, and modern UI patterns. + +--- + +### Step 3: Save Output + +Call the `write_assessment_result` tool with the following parameters: +- `resultJson`: the detection results as a JSON string +- `assessmentDir`: the value of the `assessment_dir` variable + +The JSON must strictly follow this format: + +```json +{ + "outdatedFrameworks": [ + { + "name": "Apache Struts 2", + "old": "struts2", + "new": "spring-boot", + "status": "end-of-life", + "detectedVersion": "2.5.30", + "migrationComplexity": "high", + "detectedIn": { + "configFiles": ["path/to/pom.xml"], + "sourceFiles": ["path/to/Action.java", "path/to/Login.jsp"] + }, + "alternatives": [ + "Spring Boot + Spring MVC", + "Quarkus", + "Micronaut" + ], + "explanation": "Apache Struts has reached end-of-life and no longer receives security patches or bug fixes. It has a history of critical remote code execution vulnerabilities (e.g., CVE-2017-5638, CVE-2023-50164) that made it one of the most exploited frameworks in the Java ecosystem. Continued use exposes the application to known, unpatched attack vectors. Migration to a modern, actively maintained framework such as Spring Boot is strongly recommended to ensure ongoing security support and access to current Java platform features." + } + ] +} +``` + +**JSON field descriptions:** +- `name`: Full framework name (with version distinction, e.g. "Apache Struts 1" vs "Apache Struts 2") +- `old`: Short identifier of the legacy framework (e.g. `struts2`, `winforms`) +- `new`: Short identifier of the recommended primary alternative (e.g. `spring-boot`, `maui`, `blazor`) +- `status`: `"end-of-life"` | `"deprecated"` | `"unmaintained"` | `"security-vulnerability"` | `"critical-bug"` | `"runtime-incompatible"` +- `detectedVersion`: Version string detected from config files, or `null` if not determinable +- `migrationComplexity`: `"high"` | `"medium"` | `"low"` +- `detectedIn.configFiles`: List of config file paths where references were detected +- `detectedIn.sourceFiles`: List of source file paths where references were detected +- `alternatives`: List of all recommended alternative frameworks +- `explanation`: Fixed explanation text for why this framework needs to be upgraded — use the exact text specified in each target's "Fixed explanation" section above + +If no outdated frameworks are detected, pass `{"outdatedFrameworks": []}` to the tool. + +**The JSON passed to the tool must contain ONLY the JSON object described above. No other text or formatting.** + +## Error Handling + +- **Unsupported project type**: Output a single line: `> ERROR: Unsupported project type. This skill supports Java, .NET, JavaScript, and TypeScript projects only.` +- **No build files found**: Output: `> ERROR: No recognized build files found at {workspace-path}. Verify the path is correct.` +- **Insufficient info**: Generate a best-effort report from available data. Set `detectedVersion` to `null` for dependencies where the version cannot be determined. + +## Success Criteria + +- All detection targets are checked against both configuration files and source code +- No frameworks or dependencies beyond Apache Struts and WinForms are reported +- JSON is valid and follows the specified schema exactly +- Only high-confidence findings are reported — no guesses or uncertain cases +- Result saved via `write_assessment_result` tool diff --git a/.github/skills/reconcile-tasks-with-plan/SKILL.md b/.github/skills/reconcile-tasks-with-plan/SKILL.md new file mode 100644 index 000000000..c63c67210 --- /dev/null +++ b/.github/skills/reconcile-tasks-with-plan/SKILL.md @@ -0,0 +1,46 @@ +--- +name: reconcile-tasks-with-plan +description: Update tasks.json to match the current plan.md content without modifying plan.md +--- + +# Reconcile tasks.json with plan.md + +This skill synchronizes tasks.json with plan.md when the plan has been manually updated. +It reads the current plan.md and tasks.json, detects differences, and rewrites tasks.json +to reflect the plan. **Do NOT modify plan.md** — it is the source of truth for task intent. + +## User Input + +- modernization-work-folder (Mandatory): The folder containing plan.md and .metadata/tasks.json +- language (Mandatory): The programming language of the project (java or dotnet) + +## Workflow + +1. **Read current state**: Read the following files from `${modernization-work-folder}`: + - `plan.md` — the authoritative modernization plan (source of truth for task intent) + - `.metadata/tasks.json` — the current structured task list (source of truth for execution state) + +2. **Detect inconsistencies**: Compare plan.md against the tasks defined in .metadata/tasks.json. Look for: + - Tasks described in plan.md that are missing from tasks.json + - Tasks in tasks.json that are no longer mentioned in plan.md + - Changes in task descriptions, requirements, scope, or ordering between the two files + +3. **Update tasks.json**: If inconsistencies are found, rewrite .metadata/tasks.json to match plan.md: + - Refer to the json schema tasks-schema.json for the correct task structure + - Add new tasks that appear in plan.md but are missing from tasks.json + - Remove tasks that are no longer described in plan.md + - Update descriptions, requirements, or ordering to match plan.md + - **Preserve task IDs**: For tasks that clearly map to existing entries, keep the same task ID + - **Preserve execution state**: Do NOT reset tasks that already have a `status` of "success", "failed", or "skipped". Keep their `status`, `taskSummary`, and `successCriteriaStatus` unchanged + - **Preserve metadata**: Keep the `metadata` block unchanged (same `planName`, `createdAt`, etc.) + - Follow the same task breakdown rules and schema conventions as `create-modernization-plan` + - Consult `java-upgrade-guideline.md` or `dotnet-upgrade-guideline.md` as applicable for upgrade tasks + +4. **Skip if consistent**: If no meaningful inconsistencies are found between plan.md and tasks.json, do not write any files. Simply respond that no changes were needed. + +## Important Notes + +- **Never modify plan.md** — it is the user's manually edited source of truth +- Only modify tasks that are genuinely affected by changes in plan.md +- Each task must remain independently testable +- Do not change task types unless the plan.md clearly indicates a different type diff --git a/.github/skills/repository-dependency-graph/SKILL.md b/.github/skills/repository-dependency-graph/SKILL.md new file mode 100644 index 000000000..76b1e1dd4 --- /dev/null +++ b/.github/skills/repository-dependency-graph/SKILL.md @@ -0,0 +1,250 @@ +--- +name: repository-dependency-graph +description: Analyze the service topology of a multi-repository application. Given an application name and repository paths, identifies each independently deployable service, infers each service's role and language, detects inter-service dependencies, and produces a topology graph (Markdown). +--- + +# Repository Dependency Graph + + + +Analyze the service topology of the application identified by the `name` parameter. For each +repository in `entries`, identify the independently deployable services it contains, infer their +roles and languages, and detect inter-service dependencies. Produce a topology graph in Markdown +format and save it to `{report-dir}/application-topology-graph.md`. + +## Input Parameters + +- `name` (required): The short, human-readable display name for the application. Used as the + report heading and in the service table. +- `entries` (required, list): One or more repository entries belonging to this application. Each + entry is an object with: + - `repoName` (required): The canonical display name for the repository (e.g. `order-service`). + Used in the topology graph, service table, and as the basis for slug generation. Takes + precedence over the directory name for all naming and dependency-matching purposes. + - `path` (required): The local filesystem path to the repository root. + + At least one entry must have an accessible `path`. +- `report-dir` (required): The directory where the output file is written. The directory is + created and managed by the CLI runner; the skill must not modify its path or structure. + +**Output filename**: `{report-dir}/application-topology-graph.md` + +## Execution Steps + +### Step 1: Validate Inputs + +1. Verify `name` is non-empty. If empty, abort with: + `> ERROR: The 'name' parameter is required and must not be empty.` +2. For each entry in `entries`, verify both `repoName` and `path` are present and non-empty. If + either is missing, abort with: + `> ERROR: Each entry in 'entries' must have both 'repoName' and 'path' fields.` +3. For each entry in `entries`, check whether `path` is accessible (exists and is readable). + - Collect accessible entries into a working set. + - Collect inaccessible entries into an `inaccessible` list for reporting. +4. If the working set is empty (all paths are inaccessible), abort with: + `> ERROR: Topology analysis skipped for '{name}': none of the provided repository paths could be accessed.` +5. If `report-dir` does not exist or is not writable, abort with: + `> ERROR: Output directory '{report-dir}' does not exist or is not writable.` + +### Step 2: Per-Repository Service Identification + +For each accessible entry in `entries`, identify independently deployable services using `entry.path` +as the filesystem root. Use `entry.repoName` as the canonical repository name — it replaces +directory-name inference for display labels, slug generation, and cross-repository dependency +matching. + +**Build file detection** (determines language, framework, and service candidates): + +| Build file | Language | Framework hints | +|------------|----------|-----------------| +| `pom.xml` | java | Check `` for `spring-boot-starter-parent` → Spring Boot; `quarkus-bom` → Quarkus; `micronaut-parent` → Micronaut | +| `build.gradle` / `build.gradle.kts` | java | Check `plugins {}` for `org.springframework.boot`, `io.quarkus`, `io.micronaut.application` | +| `*.csproj` | dotnet | Check `` and packages for `Microsoft.AspNetCore.*` → ASP.NET Core; `Microsoft.Azure.Functions.Worker` → Azure Functions | +| `*.sln` / `*.slnx` | dotnet | Multi-project solution; recurse into sub-projects | +| `package.json` (with `"start"` script or `"main"` field) | javascript/typescript | Check `"dependencies"` for `express`, `fastify`, `koa`, `@nestjs/core`, `next`, `react`, `vue`, `@angular/core` | +| `tsconfig.json` (without `package.json`) | typescript | Infer TypeScript library | + +**Service identity** (one candidate per independently deployable unit): + +- For single-service repos: use `entry.repoName` as the service name. +- For monorepos: use `entry.repoName` as the repository label; detect multiple deployable units by looking for: + - Multiple `Dockerfile` files at different subdirectory levels (each names a service) + - Multiple `*.csproj` files with `Exe` or `exe` + - Multiple `pom.xml` files in subdirectories (each with its own ``) + - Multiple `package.json` files with a `"start"` or `"main"` script at subdirectory level + - Each detected unit becomes a separate Service entry; `subPath` is set to its relative path within the repo. + +**ServiceRole inference rules** (apply in order; first match wins): + +| Role | Evidence | +|------|----------| +| `ApiGateway` | Name or directory contains `gateway`, `proxy`, `edge`, `ingress`; OR Spring Cloud Gateway / Netflix Zuul dependency detected | +| `Frontend` | Framework is React, Angular, Vue, Blazor, Next.js, Nuxt; OR name contains `ui`, `web`, `frontend`, `portal`, `dashboard` | +| `Worker` | No inbound HTTP port exposed in Dockerfile; OR name contains `worker`, `consumer`, `processor`, `job`, `scheduler`, `daemon`; OR Spring Batch / Azure WebJobs / Hangfire dependency detected | +| `DataService` | Name contains `db`, `data`, `repository`, `store`, `cache`; OR heavy ORM usage (Spring Data JPA, EF Core DbContext is >50% of dependencies) | +| `SharedLibrary` | No `Dockerfile` and no executable entry point; name contains `lib`, `shared`, `common`, `sdk`, `client` | +| `WebApi` | HTTP framework detected (Spring MVC, ASP.NET Core controllers, Express, NestJS, FastAPI) and none of the above matched | +| `Unknown` | None of the above rules matched | + +**Service ID**: Lowercase slug of service name (replace spaces, dots, underscores with hyphens; remove non-alphanumeric except hyphens). Must be unique within the application. + +### Step 3: Cross-Repository Dependency Detection + +For each service, scan the following configuration and build files for references to other service names/identifiers discovered in Step 2: + +**Files to scan**: +- `application.properties`, `application.yml`, `application.yaml` (Spring Boot) +- `appsettings.json`, `appsettings.*.json` (ASP.NET Core) +- `*.env`, `.env`, `.env.*` +- `docker-compose.yml`, `docker-compose.yaml`, `docker-compose.*.yml` +- Helm chart `values.yaml`, `values.*.yaml` +- Kubernetes manifests (`*.yaml` in `k8s/`, `kubernetes/`, `deploy/`, `manifests/` directories) +- `pom.xml` (Maven dependencies — detect another service's `artifactId` as a dependency) +- `*.csproj` (NuGet `` — detect another service's project/package name) +- `package.json` (`dependencies` / `devDependencies` — detect another service's package name) + +**What to look for**: + +| DependencyType | Evidence | +|----------------|----------| +| `HttpCall` | Property value matching a pattern like `http(s)://{service-name}`, `{service-name}.default.svc`, `{service-name}:PORT`, or an environment variable named `{SERVICE_NAME}_URL` / `{SERVICE_NAME}_HOST` / `{SERVICE_NAME}_BASE_URL` | +| `MessageQueue` | Property key or value containing a topic/queue name that also appears in another service's config (e.g., `spring.kafka.topic`, `rabbitmq.queue`, `azure.servicebus.topic`) | +| `SharedLibrary` | Build file dependency whose artifact/package name matches another service's slug or name (common for `SharedLibrary`-role services) | +| `DirectReference` | `depends_on:` in docker-compose; Kubernetes service reference; direct project reference in `.sln` / `*.csproj` | + +For each detected dependency, record: +- `targetServiceId` (the other service's slug) +- `dependencyType` (from the table above) +- `evidence` (the config file path and key/value that triggered the match, e.g., `appsettings.json: ServiceUrls:OrderService = http://order-service`) + +Only record dependencies where **both** the source service and the target service are within the analyzed set. Do not record dependencies on external services (databases, cloud services, etc.) — those are covered by the `dependency-map` skill. + +### Step 4: Cycle Detection + +Perform a depth-first search (DFS) on the directed dependency graph. + +- Maintain a visited set and a recursion stack. +- When a back edge is detected (a node already in the recursion stack is encountered again), record the cycle as a warning string: + `"Circular dependency detected: {ServiceA} → {ServiceB} → ... → {ServiceA}"` +- Continue DFS after recording the cycle (do not abort). +- Collect all cycle warning strings into `cycleWarnings`. + +### Step 5: Duplicate Service Detection + +For each pair of services, check: +1. Are their slugified names identical? (e.g., `order-service` and `order-service`) +2. Do their names differ only by separator or case after normalization? (e.g., `orderservice` == `order_service` == `OrderService`) +3. Do their primary entry-point artifact names match? (e.g., same Maven ``, same .NET assembly name) + +If any condition is true, add to that service's `warnings` list: +`"WARNING: Potential duplicate of '{other-service-name}' (from {other-repo-name} at {other-repo-path}). Verify these are not the same service."` + +### Step 6: Generate Mermaid Diagram + +Build a `flowchart TD` diagram. + +**Node format**: `{ServiceId}["{ServiceName}\n({Role})"]` +- Example: `orderSvc["order-service\n(WebApi)"]` + +**Edge format** (normal dependency): +`{SourceId} -->|"{DependencyType}"| {TargetId}` +- Example: `apiGateway -->|"HttpCall"| orderSvc` + +**Cycle edge format**: +`{SourceId} -.->|"⚠ cycle"| {TargetId}` + +**Node label rules**: +- Use plain text only; avoid `@`, `#`, `$`, `%`, `&`, `<`, `>`, `"` inside node labels +- Replace double quotes in names with single quotes before embedding in labels +- All node IDs must be unique across the diagram + +**Scale limit (40-node rule)**: +- If total service count ≤ 40: show all services individually. +- If total service count > 40: collapse per-repository into aggregate nodes: + - `{repoSlug}Grp["{repoName}\n({N} services)"]` where `{repoSlug}` is the slug of `entry.repoName` + - Show only cross-repository edges between aggregate nodes. + - Add a note at the top of the diagram section: `> Note: {N} services exceed the 40-node display limit. Services are grouped by repository.` + +### Step 7: Render Output + +Produce the final output content: + +**Markdown format** : + +``` +# Topology Graph: {name} + +Analyzed {N} service(s) across {M} repositor(y/ies) for application "{name}" on {YYYY-MM-DD HH:MM UTC}. + +## Services + +```mermaid +{mermaid diagram content} +``` + +## Service Details + +| Service | Role | Language | Source Repository | Warnings | +|---------|------|----------|------------------|----------| +| {service-name} | {Role} | {language} | {repoName} | {warnings or —} | +... + +## Warnings + +- {cycle warning 1} +- {cycle warning 2} +- {duplicate warning} +- Inaccessible repository: {repoName} ({path}) (skipped) +``` +(Omit `## Warnings` section entirely when no warnings exist.) + +### Step 8: Save Output + +1. Compute the output filename: + - Full path: `{report-dir}/application-topology-graph.md` +2. Write the rendered content to the output file (create or overwrite). +3. Log: `Topology graph for '{name}' saved to {output-path}` + +## Scaling Rules + +- Maximum 40 Mermaid nodes for GitHub rendering compatibility and diagram legibility. +- When service count > 40: collapse to per-repository aggregate nodes (Step 6 scale limit rule). +- Performance expectation: analysis of 3–10 repositories should complete in under 5 minutes on a + standard developer workstation. +- For very large monorepos (>20 sub-projects), limit per-repo service detection to a maximum of + 10 services; add a note in the Warnings section: `Note: Repository '{repo}' contains more than + 10 detected services; only the first 10 are shown.` + +## Mermaid Syntax Rules + +- Use `flowchart TD` (top-down layout). +- Avoid special characters in node labels: `@`, `#`, `$`, `%`, `&`, `<`, `>`. +- Always quote arrow labels with double quotes: `-->|"label"|`. +- Use `-.->` (dotted arrow) for cycle edges with label `"⚠ cycle"`. +- All node IDs must be unique across the entire diagram. +- Do not use `subgraph` — flat node layout only (services are already grouped logically by role + via node labels). + +## Error Handling + +| Condition | Behavior | +|-----------|----------| +| All `entries` inaccessible | Abort with error message (Step 1). No output file written. | +| Subset of paths inaccessible | Continue with accessible subset. Add each inaccessible entry as `Inaccessible repository: {repoName} ({path}) (skipped)` in Warnings section. | +| Missing `repoName` or `path` field | Abort with error message (Step 1). No output file written. | +| No build files found in any repository | Best-effort: list each repo as a service with `role: Unknown` and `language: unknown`. Add note in Warnings: `Note: No recognized build files were found. Service roles could not be inferred.` | +| Unsupported project type | Set `language: unknown` and `role: Unknown` for that service. Do not fail. | +| Service name collision (duplicate IDs after slugification) | Append the `repoName` as a suffix: `{slug}-{repo-name-slug}`. | +| Output file write error | Log error: `Failed to write topology graph for '{name}' to {report-dir}/application-topology-graph.md: {error}`. Do not abort the parent process. | + +## Success Criteria + +- Output file `{report-dir}/application-topology-graph.md` is created and non-empty. +- File contains at least one service node in the Mermaid diagram. +- All inaccessible repository paths are listed in the Warnings section (none silently ignored). +- The Mermaid diagram contains no syntax errors (valid `flowchart TD` syntax). +- The Service Details table has one row per detected service. +- The Warnings section is present only when at least one warning exists. +- No unhandled exceptions propagate to the assessment pipeline. + + diff --git a/.github/skills/rulebook-create/SKILL.md b/.github/skills/rulebook-create/SKILL.md new file mode 100644 index 000000000..b352b3bea --- /dev/null +++ b/.github/skills/rulebook-create/SKILL.md @@ -0,0 +1,89 @@ +--- +name: rulebook-create +description: Generate or update modernization rulebook from document sources. Use this skill when the user wants to create a rulebook, sync rulebook from a document or GitHub issue, extract migration policies from architecture docs, or update existing rulebook files with new decisions. +--- + +# Rulebook Create + +Analyze source documents and generate a modernization rulebook — three markdown files that capture an organization's modernization strategy, approved migration targets, and enforceable policies (standards + guardrails). + +## User Input + +- **output-path** (Required): The folder to save the rulebook files. +- **source-file-path** (Optional): Path to the source file or directory containing the document content +- **use-defaults** (Optional): When `true`, read the built-in default rulebook files as reference to fill gaps in the generated rulebook. User source takes priority when there is a conflict. Default files: [charter-default](references/charter-default.md), [targets-default](references/targets-default.md), [policies-default](references/policies-default.md) + +## Output Structure + +``` +${output-path}/ +├── charter.md # Scope, 6R modernization strategy, and principles +├── targets.md # Approved target technologies, library mappings, and target artifacts +└── policies.md # Standards + guardrails: naming, security, compliance, prohibited/required tech, validation gates +``` + +## Principles + +- **Source Fidelity**: The rulebook is loaded and enforced by automated agents at runtime — a fabricated policy causes wrong migration decisions. Only include content explicitly present in the source document or user prompt. If a category isn't mentioned, omit that section entirely rather than adding placeholders. +- **Incremental Merge**: When output files already exist, merge at the **section level** — update sections with new or changed content, preserve unchanged sections verbatim. If the source explicitly removes or contradicts an existing entry, update that entry. Never drop existing content simply because the source is silent on it. +- **Direct Policy Output**: State policies as-is. Do not include rationale, explanations, or implementation guidance — those belong elsewhere (skills). + +## Classification Principles + +Each file serves a distinct purpose and is consumed at different stages of the modernization workflow: + +- **charter.md** — Rulebook metadata (name, version), scope (covered applications, languages constraints), 6R modernization strategy (Rehost / Replatform / Refactor decision table with override conditions — Retire, Retain, Repurchase are outside app modernization scope), and guiding principles. Loaded during assessment and plan creation to determine what to modernize and what strategy to apply. +- **targets.md** — Approved framework versions, compute/data/integration services, source-to-target library mappings, and target artifacts (container base images). Loaded during assessment (to define target services to assess against) and plan creation (to determine what tasks to generate). +- **policies.md** — Naming & metadata standards, security requirements, compliance requirements, guardrails (prohibited technologies/patterns, required elements, region constraints), validation & quality gates, and coding style guidelines. Loaded during assessment, plan creation, and execution to enforce standards and guardrails. + +## Workflow + +### Step 1: Read and Analyze Source + +1. If ${source-file-path} is provided, read the content (may be a GitHub issue export, markdown file, or a directory of files — if a directory, read all files recursively) +2. If ${use-defaults} is `true`, read the default rulebook files ([charter-default](references/charter-default.md), [targets-default](references/targets-default.md), [policies-default](references/policies-default.md)) as reference context. When the user source and a default conflict, the user source takes priority. +3. Check if output files already exist in ${output-path} — read them for merge comparison +4. Classify each decision from the source (and/or the user prompt) into one of the three output files using the Classification Guide above + +If no source file is provided, work with the user prompt and any existing rulebook files only. + +### Step 2: Generate Rulebook Files + +For each file, use the corresponding template as the structural reference, then fill in content extracted from the source. +Only include sections/subsections that have meaningful, source-backed content. Do not emit empty headings, empty tables, or placeholder-only sections. + +#### charter.md + +Use the template [charter-template](charter-template.md) as a section catalog: +- Metadata, Scope, 6R Modernization Strategy (Rehost / Replatform / Refactor), Principles + +#### targets.md + +Use the template [targets-template](targets-template.md) as a section catalog: +- Target Frameworks, Target Compute Services, Target Data Services, Target Integration Services, Target Libraries, Target Artifacts + +#### policies.md + +Use the template [policies-template](policies-template.md) as a section catalog: +- Naming & Metadata Standards, Security Requirements, Compliance Requirements, Guardrails (Hard Boundaries), Validation & Quality Gates, Coding Style Guidelines + +For each file: if it already exists, merge new content; if not, create it fresh. + +### Step 3: Validate + +Verify the output before finishing: +- [ ] All three files exist in ${output-path} +- [ ] Each file includes only sections with meaningful content from the source +- [ ] No empty headings, empty tables, or placeholder-only sections remain +- [ ] Every decision in the source document is reflected in at least one output file +- [ ] No content was invented beyond what the source provides + +### Step 4: Present Summary + +Report to the user: +- Number of target technologies defined +- Number of library migration mappings captured +- Number of prohibited technologies/patterns +- Number of required elements +- Number of validation/quality gates +- Sections omitted due to missing source content — flag these as gaps for architect review diff --git a/.github/skills/rulebook-create/charter-template.md b/.github/skills/rulebook-create/charter-template.md new file mode 100644 index 000000000..e91281fee --- /dev/null +++ b/.github/skills/rulebook-create/charter-template.md @@ -0,0 +1,36 @@ +# Charter + +## Metadata + +| Field | Value | +|-------|-------| +| Rulebook Name | | +| Version | | +| Changelog | | + +## Scope + +### Covered Applications and Languages + +Only .NET and Java applications are in scope. + +### Application Types + +**Included:** + +**Excluded:** + +### Custom Libraries + +### Constraints + +## Modernization Strategy (6R Guidelines) + +Of the 6R strategies, this rulebook covers **Rehost**, **Replatform**, and **Refactor** — the three that involve app modernization. Retire, Retain, and Repurchase are outside the scope of app modernization. + +Supported strategies: **Rehost** (lift-and-shift, no code changes), **Replatform** (minimal code changes — containerize, adopt managed services), **Refactor** (modify code/architecture — decompose, upgrade). + +| Application Type | Default Strategy | Override Conditions | +|------------------|-----------------|---------------------| + +## Principles diff --git a/.github/skills/rulebook-create/policies-template.md b/.github/skills/rulebook-create/policies-template.md new file mode 100644 index 000000000..c41acce73 --- /dev/null +++ b/.github/skills/rulebook-create/policies-template.md @@ -0,0 +1,70 @@ +# Policies + +Enforceable standards and hard boundaries for .NET and Java modernization. Every policy here is validatable against generated artifacts. + +## Naming & Metadata Standards + +### Resource Naming Patterns + +| Resource Type | Pattern | Example | +|--------------|---------|---------| + +### Tagging Requirements + +| Tag | Required | Description | +|-----|----------|-------------| + +## Security Requirements + +### Authentication & Authorization + +### Secrets Management + +### Network Security + +### Encryption + +## Compliance Requirements + +### Applicable Frameworks + +| Framework | Key Constraints | +|-----------|----------------| + +### Data Classification + +## Guardrails (Hard Boundaries) + +### Prohibited Technologies + +| Technology | Reason | Approved Alternative | +|-----------|--------|---------------------| + +### Prohibited Patterns + +| Pattern | Reason | Approved Alternative | +|---------|--------|---------------------| + +### Required Elements + +Every modernized application must include: + +#### Cloud Resources + +#### Monitoring + +### Approved Regions / Residency Constraints + +## Validation & Quality Gates + +### Required Scanners/Tools + +### Pipeline Gates + +### Confidence Thresholds + +## Coding Style Guidelines + +### .NET + +### Java diff --git a/.github/skills/rulebook-create/references/charter-default.md b/.github/skills/rulebook-create/references/charter-default.md new file mode 100644 index 000000000..49084775a --- /dev/null +++ b/.github/skills/rulebook-create/references/charter-default.md @@ -0,0 +1,59 @@ +# Charter — Default Modernization Rulebook + +## Metadata + +- **Name:** `azure-app-modernization-default` +- **Description:** Default rulebook for modernizing .NET and Java applications to Azure App Service, Container Apps, or AKS + +--- + +## Scope + +**Included:** Java (8+), .NET Framework / .NET 6+ — web apps, REST/gRPC APIs, background workers, event-driven microservices. Custom libraries used by in-scope apps are in-scope. + +**Excluded:** Mainframe/COBOL, desktop apps (WinForms/WPF/Electron), embedded/IoT, standalone database migrations, standalone data pipelines/ETL, Node.js and Python applications. + +**Constraints:** + +| Constraint | Rule | +|---|---| +| Traffic | Production apps require zero-downtime migration (blue-green, canary) | +| Timelines | Target 90-day execution cycles per wave | +| Compliance | Regulated apps (PCI-DSS, HIPAA, FedRAMP) must preserve compliance posture | + +--- + +## Modernization Strategy (6R Guidelines) + +Of the 6R strategies, this rulebook covers **Rehost**, **Replatform**, and **Refactor** — the three that involve app modernization. Retire, Retain, and Repurchase are outside the scope of app modernization. + +Supported strategies: **Rehost** (lift-and-shift, no code changes), **Replatform** (minimal code changes — containerize, adopt managed services), **Refactor** (modify code/architecture — decompose, upgrade). + +| App Type | Strategy | Target | Override | +|---|---|---|---| +| Stateless web apps / APIs (containerized) | Replatform | Container Apps | Complex orchestration → AKS | +| Stateless web apps / APIs (non-containerized) | Replatform | App Service | — | +| Apps with OS-level dependencies (COM, registry, custom installers) | Rehost | App Service Managed Instance | Can containerize → Container Apps or AKS | +| Stateful monoliths | Refactor | Container Apps + backing services | Cannot decompose in timeline → App Service | +| Legacy (EOL framework) | Refactor | Container Apps on supported runtime | OS dependencies → App Service MI | +| Background workers / jobs | Replatform | Container Apps | Complex orchestration → AKS | +| Event-driven / messaging | Replatform | Container Apps + Service Bus | Complex orchestration → AKS | + +**Decision sequence:** Match app to table row → apply default unless override met → escalate to architecture review if no match. + +--- + +## Principles + +| # | Principle | Rule | +|---|---|---| +| 1 | Cloud-first | Target Azure PaaS by default; IaaS only when required | +| 2 | Zero data loss | Validated data migration and rollback plan before cutover | +| 3 | Reversibility | Prefer reversible steps; no one-way doors without sign-off | +| 4 | Least privilege | Azure RBAC + managed identities; no shared credentials | +| 5 | Infrastructure as code | All infra in Bicep or Terraform; no portal-only resources | +| 6 | Observability | Structured logs, metrics, distributed traces via Azure Monitor | +| 7 | Managed services | Managed databases, caches, brokers over self-hosted | +| 8 | Minimize blast radius | Small independent waves; resource group isolation | +| 9 | Cost awareness | Right-size resources; consumption SKUs; tag for cost attribution | +| 10 | Security by design | Defender for Cloud, TLS 1.2+, Entra ID authentication | diff --git a/.github/skills/rulebook-create/references/policies-default.md b/.github/skills/rulebook-create/references/policies-default.md new file mode 100644 index 000000000..1ba737e54 --- /dev/null +++ b/.github/skills/rulebook-create/references/policies-default.md @@ -0,0 +1,123 @@ +# Policies — Default Modernization Rulebook + +Enforceable standards and hard boundaries for .NET and Java modernization. Every policy here is validatable against generated artifacts. + +--- + +## Naming & Metadata Standards + +**Pattern:** `{env}-{app}-{service}-{resourcetype}` +Example: `prod-contoso-api-app`, `prod-contoso-shared-kv` + +| Segment | Values | +|---|---| +| `{env}` | `dev`, `stg`, `prod` | +| `{app}` | Workload identifier | +| `{service}` | Component name (`api`, `web`) | +| `{resourcetype}` | CAF abbreviation (`rg`, `app`, `kv`) | + +**Required tags** on every resource: + +| Tag | Example | +|---|---| +| `environment` | `dev`, `staging`, `production` | +| `owner` | `platform-team` | +| `cost-center` | `CC-4200` | +| `workload` | `contoso-orders` | +| `migration-wave` | `wave-2` | + +--- + +## Security Requirements + +**Identity:** Managed Identity required for all service-to-service auth. Use `DefaultAzureCredential`. Connection strings with embedded keys (`AccountKey=`, `SharedAccessKey=`) are prohibited. Service principal secrets permitted only for CI/CD with 90-day rotation. + +**Secrets:** Azure Key Vault required. No secrets in source, config files, or env vars. Key Vault access via RBAC (Secrets User / Crypto User), not access policies. + +**Network:** Private endpoints for all data services. NSGs on every subnet. No public IPs on workloads — route through App Gateway, Front Door, or APIM. TLS 1.2+ enforced; TLS 1.0/1.1 prohibited. + +**Encryption:** At rest — platform-managed keys default; CMK via Key Vault for confidential/regulated data. In transit — TLS 1.2+ mandatory. + +--- + +## Compliance Requirements + +**Baseline:** WAF and CIS Azure Foundations Benchmark. + +**Data classification:** + +| Level | Applies | Controls | +|---|---|---| +| General | Default | Standard encryption | +| Confidential | PII, financial, health | CMK, audit logging, restricted access | + +**Data residency:** Approved regions only (see Guardrails). No cross-region replication without override. + +**Audit logging:** Diagnostic settings on all data-plane operations. Central Log Analytics workspace. 90-day online retention, 1-year archive. + +--- + +## Guardrails (Hard Boundaries) + +### Prohibited Technologies + +| Technology | Reason | +|---|---| +| FTP / FTPS | Insecure; use managed storage | +| Basic authentication | Credential exposure | +| Self-signed certs (prod) | Trust chain violation | +| Legacy Azure SDKs (Track 1) | Deprecated | +| .NET Framework < 4.8 | EOL | +| Java < 11 | EOL | +| log4j 1.x | EOL, multiple known vulnerabilities | + +### Prohibited Patterns + +| Pattern | Reason | +|---|---| +| Hardcoded secrets/credentials | Security violation | +| Embedded connection keys | Use Managed Identity | +| `SELECT *` | Performance/coupling risk | + +### Required Elements + +Every generated service code must include: +- `/health` (liveness) and `/ready` (readiness) endpoints +- OpenTelemetry or Azure Monitor structured logging +- Graceful shutdown (SIGTERM / cancellation token) +- Retry with exponential backoff + jitter for external calls + +### Approved Regions + +`eastus`, `eastus2`, `westus2`, `westeurope`, `northeurope` — override per org as needed. + +--- + +## Validation & Quality Gates + +**Required scanners:** Container image scanning (Trivy or equivalent), dependency vulnerability scanning (Dependabot or equivalent). + +**Pipeline gates:** Build, unit tests, code analysis, dependency scan, container scan, staging deployment, smoke tests, production deployment with approval gate. Minimum 80% code coverage. + +**Confidence thresholds:** No High or Critical findings in dependency scan before production deployment. + +--- + +## Coding Style Guidelines + +General principles: + +- `.editorconfig` required; generate from framework conventions if missing +- Descriptive names, no abbreviations unless domain-standard +- Prefer async/non-blocking patterns; 120-char soft line limit +- 4-space indent, LF line endings + +### .NET +- Sync-over-async (`Task.Result`, `.GetAwaiter().GetResult()`) — deadlock risk +- `new HttpClient()` — socket exhaustion; use `IHttpClientFactory` +- `Thread.Sleep` in async paths — blocks threadpool; use `Task.Delay` +- Catch-all `catch(Exception)` without rethrow — swallows errors + +### Java +- Blocking I/O in reactive pipelines — use `CompletableFuture` or reactive alternatives +- Catch-all `catch(Exception)` without rethrow — swallows errors diff --git a/.github/skills/rulebook-create/references/targets-default.md b/.github/skills/rulebook-create/references/targets-default.md new file mode 100644 index 000000000..9ebffd38e --- /dev/null +++ b/.github/skills/rulebook-create/references/targets-default.md @@ -0,0 +1,73 @@ +# Targets — Default Modernization Rulebook + +Approved target technologies for .NET and Java modernization. Defines *what is approved* — not implementation steps (those belong in skills). + +--- + +## Target Frameworks + +| Language | Target Version | EOL Versions (must upgrade) | +|---|---|---| +| Java | 21+ LTS, Spring Boot 3.x | Java 8 EOL; 11/17 supported but 21+ preferred | +| .NET | 8+ LTS | .NET Framework 4.x, .NET Core 3.1, .NET 6 end-of-support | + +--- + +## Target Compute Services + +| Workload Type | Target | Selection Rule | +|---|---|---| +| Containerized stateless apps / APIs | **Azure Container Apps** | Scale-to-zero, managed ingress, Dapr/KEDA; no full K8s control plane needed | +| Complex orchestration, multi-container | **Azure Kubernetes Service (AKS)** | Full K8s control plane, custom networking, service mesh, DaemonSets | +| Non-containerized web apps | **Azure App Service** | Platform-managed TLS, deployment slots, integrated auth; lowest migration friction | +| Apps with OS-level dependencies | **Azure App Service Managed Instance** | COM, registry, custom installers, RDP access via Bastion; lift-and-improve with OS control | + +**Selection:** Evaluate workload characteristics against table → select matching target. No single universal default; target is determined by app profile. + +--- + +## Target Data Services + +Backing services for modernized applications — not standalone migration targets. + +| Source | Target | When | +|---|---|---| +| SQL Server | **Azure SQL Database** | Default; Managed Instance only if instance-scoped features needed | +| PostgreSQL | **Azure Database for PostgreSQL Flexible Server** | Default for PostgreSQL | +| MySQL | **Azure Database for MySQL Flexible Server** | Default for MySQL | +| Oracle, other RDBMS | Evaluate case-by-case | Prefer Azure SQL or PostgreSQL | +| MongoDB, DynamoDB, document stores | **Azure Cosmos DB** | Multi-model NoSQL; select appropriate API | +| Memcached, self-hosted Redis, in-process caches | **Azure Cache for Redis** | Distributed caching and session state | +| File shares, NFS, local disk | **Azure Blob Storage** | Unstructured data; Hot/Cool/Archive tiers | +| SMB file shares | **Azure Files** | When SMB/NFS mount semantics required | + +--- + +## Target Integration Services + +Backing services — selected when the application's messaging/eventing/API layer migrates alongside the app. + +| Source | Target | When | +|---|---|---| +| RabbitMQ, MSMQ, ActiveMQ, IBM MQ | **Azure Service Bus** | Reliable enterprise messaging (queues, topics, sessions, dead-letter) | +| Custom webhooks, polling | **Azure Event Grid** | Reactive event-driven, push delivery | +| Kafka, high-throughput streaming | **Azure Event Hubs** | High-throughput ingestion; Kafka protocol compatible | +| Custom reverse proxies, legacy gateways | **Azure API Management** | API facade, rate limiting, auth, developer portal | + +--- + +## Target Libraries + +Source → target mappings for .NET and Java task generation. + +| Category | Source | Target | +|---|---|---| +| Azure SDK (.NET) | `Microsoft.Azure.*` (Track 1) | `Azure.*` (Track 2) | +| Azure SDK (Java) | `com.microsoft.azure:*` | `com.azure:*` | +| Logging (.NET) | log4net, NLog | OpenTelemetry + Azure Monitor | +| Logging (Java) | log4j 1.x, SLF4J + Logback | OpenTelemetry + Azure Monitor | +| Auth | Connection strings, hardcoded keys | `DefaultAzureCredential` (Managed Identity) | +| Config (.NET) | `web.config`, `app.config`, hardcoded | Azure App Configuration + Key Vault | +| Config (Java) | `application.properties`, hardcoded | Azure App Configuration + Key Vault | +| DI (.NET) | Autofac, Unity, Ninject | Built-in `Microsoft.Extensions.DependencyInjection` | +| HTTP (.NET) | Raw `HttpClient`, `WebClient`, `RestSharp` | `IHttpClientFactory` with resilience policies | diff --git a/.github/skills/rulebook-create/targets-template.md b/.github/skills/rulebook-create/targets-template.md new file mode 100644 index 000000000..0ac0dd15d --- /dev/null +++ b/.github/skills/rulebook-create/targets-template.md @@ -0,0 +1,35 @@ +# Targets + +Approved target technologies for .NET and Java modernization. Defines *what is approved* — not implementation steps. + +## Target Frameworks + +| Language | Target Version | Notes | +|----------|---------------|-------| + +## Target Compute Services + +| Platform | Use When | +|----------|----------| + +## Target Data Services + +| Service | Use When | +|---------|----------| + +## Target Integration Services + +| Service | Use When | +|---------|----------| + +## Target Libraries + +Source → target mappings for .NET and Java. + +| Category | Source | Target | Notes | +|----------|--------|--------|-------| + +## Target Artifacts + +| Artifact | Location | Notes | +|----------|----------|-------| diff --git a/.github/skills/security-assessment-merge/SKILL.md b/.github/skills/security-assessment-merge/SKILL.md new file mode 100644 index 000000000..da58db577 --- /dev/null +++ b/.github/skills/security-assessment-merge/SKILL.md @@ -0,0 +1,318 @@ +--- +name: security-assessment-merge +description: Merge CVE and CWE security assessment results into a unified security report and update report.json +--- + +# Security Assessment: Merge and Report + +## Role + +You are a **security assessment aggregator**. Your task is to read all CVE and CWE result files produced by earlier security skills, merge them into a unified security assessment report, and inject the findings into `report.json`. + +> **Important:** You are a data aggregator, NOT an analyzer. Do NOT re-scan the codebase. Your sole responsibility is to read, normalize, merge, and write the security findings that were already produced by the CVE and CWE assessment skills. + +## Working Directory + +All security assessment input and output files are located under: + +``` +.github/modernize/assessment/engines/security/ +``` + +All file paths in this skill are relative to this directory unless otherwise noted. + +## Objective + +1. Read all security assessment result files from the `.github/modernize/assessment/engines/security/` directory +2. Normalize CVE and CWE findings into a unified format +3. Generate `security-assessment.json` (machine-readable merged report) in the same directory +4. Generate `security-assessment.md` (human-readable markdown report) in the same directory +5. Inject a `"security"` array into the existing `report.json` + +## Instructions + +### Step 1: Read CVE Results + +Read the CVE result file at `.github/modernize/assessment/engines/security/cve-assessment-result.json`. + +This file is a **flat JSON array** where each element has: +```json +{ + "id": "CVE-2024-xxxx", + "name": "Summary", + "status": "FOUND", + "category": "CVE", + "severity": "critical|high|medium|low", + "storyPoint": 1, + "evidence": { + "files": ["pom.xml:42"], + "explanation": "Markdown description..." + } +} +``` + +Only include entries where `status` is `"FOUND"`. + +**Filter by minimum CVE severity:** The minimum severity threshold is provided in the assessment prompt instructions. Only include CVE findings whose severity meets or exceeds the specified threshold. The severity order from lowest to highest is: `low` < `medium` < `high` < `critical`. Exclude findings with unknown or missing severity. If no threshold was specified in the prompt, default to `high`. + +If the file does not exist or is empty, treat CVE findings as an empty list. + +### Step 2: Read CWE Results + +Read all CWE result files matching `.github/modernize/assessment/engines/security/result-cwe-*.json`. + +Each CWE file uses a **wrapper format**: +```json +{ + "input_name": "CWE - Category Name", + "status": "success", + "result": { + "values": [ + { + "id": "CWE-79", + "name": "Cross-site Scripting", + "status": "FOUND", + "category": "Injection Attacks", + "severity": "optional", + "storyPoint": 8, + "description": "The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.", + "evidence": { + "files": ["src/Controller.java"], + "explanation": "Description..." + } + } + ] + } +} +``` + +Navigate to `result.values` in each file and extract entries where `status` is `"FOUND"`. + +If no CWE files exist, treat CWE findings as an empty list. + +### Step 3: Build the Unified Security Findings Array + +Transform all FOUND CVE and CWE items into the unified format: + +```json +{ + "id": "CVE-2024-xxxx or CWE-79", + "title": "Human-readable title", + "category": "CVE or CWE category (e.g., Injection Attacks)", + "severity": "mandatory|optional|potential", + "description": "Detailed description", + "evidence": { + "files": ["path/to/file.java"], + "explanation": "Why this was flagged" + }, + "storyPoint": 1 +} +``` + +For **CVE findings**: Map `name` → `title`, use `explanation` as both `description` and `evidence.explanation`. **Map the raw CVE severity to assessment severity levels:** +- `critical` or `high` → `mandatory` +- `medium` → `optional` +- `low` or unknown → `potential` + +For **CWE findings**: Map `name` → `title`, map `severity` → `severity` (already in assessment format), map `storyPoint` → `storyPoint`, map `description` → `description`, use `explanation` as `evidence.explanation`. + +### Step 4: Calculate Summary Statistics + +Count: +- `totalFindings`: Total number of FOUND CVE + CWE items +- `cveCount`: Number of CVE findings +- `cweCount`: Number of CWE findings +- `bySeverity`: Count of findings grouped by severity (`mandatory`, `optional`, `potential`) +- `byCategory`: Count of findings grouped by category (`CVE`, `Injection Attacks`, etc.) +- `totalRulesAssessed`: Total number of CWE checklist items across all `.github/modernize/assessment/engines/security/result-cwe-*.json` files (both FOUND and NOT_FOUND) +- `rulesPassed`: `totalRulesAssessed - cweCount` + +### Step 5: Write security-assessment.json + +Write the full merged report to `.github/modernize/assessment/engines/security/security-assessment.json`: + +```json +{ + "GeneratedAt": "2026-04-09T12:00:00.0000000Z", + "ProjectPath": ".", + "Summary": { + "TotalFindings": 5, + "CveCount": 2, + "CweCount": 3, + "BySeverity": { "mandatory": 3, "optional": 1, "potential": 1 }, + "ByCategory": { "CVE": 2, "Injection Attacks": 2, "Code Quality": 1 }, + "TotalRulesAssessed": 59, + "RulesPassed": 56 + }, + "CveFindings": [ + { + "Id": "CVE-2024-xxxx", + "Name": "Vulnerability Title", + "Category": "CVE", + "Severity": "mandatory", + "StoryPoint": 1, + "Description": "...", + "Files": ["pom.xml:42"], + "Explanation": "..." + } + ], + "CweFindings": [ + { + "Id": "CWE-79", + "Name": "Cross-site Scripting", + "Category": "Injection Attacks", + "Severity": "optional", + "StoryPoint": 8, + "Description": "...", + "Files": ["src/Controller.java"], + "Explanation": "..." + } + ] +} +``` + +### Step 6: Write security-assessment.md + +Write a human-readable markdown report to `.github/modernize/assessment/engines/security/security-assessment.md`: + +```markdown +# Security Assessment Report + +**Generated:** + +## Summary + +| Metric | Count | +|--------|-------| +| Total Findings | N | +| CVE Vulnerabilities | N | +| CWE Vulnerabilities | N | +| Total Rules Assessed | N | +| Rules Passed | N | + +### By Severity + +| Severity | Count | +|----------|-------| +| mandatory | N | +| optional | N | +| potential | N | + +## CVE Findings (Dependency Vulnerabilities) + +### CVE-2024-xxxx: Title +- **Severity:** mandatory +- **Story Points:** 1 +- **Files:** pom.xml:42 + + + +## CWE Findings (Code-Level Vulnerabilities) + +### CWE-79: Cross-site Scripting +- **Category:** Injection Attacks +- **Severity:** optional +- **Story Points:** 8 +- **Files:** src/Controller.java + + +``` + +If there are no findings at all, write: +```markdown +## No security vulnerabilities found. + +The assessment did not detect any CVE or CWE vulnerabilities in the codebase. +``` + +### Step 7: Merge into report.json + +The `report.json` file lives in a versioned report directory: + +``` +.github/modernize/assessment/reports/report-{reportId}/report.json +``` + +where `{reportId}` is a `yyyyMMddHHmmss` timestamp (e.g., `20260410120000`). This directory is created by the main assessment skill. + +**Step 7a: Locate report.json** + +Find the latest versioned report directory by listing directories matching the `report-*` pattern and sorting in descending order (newest first): + +```bash +REPORTS_DIR=".github/modernize/assessment/reports" +REPORT_DIR=$(ls -d "$REPORTS_DIR"/report-* 2>/dev/null | sort -r | head -n 1) + +if [ -n "$REPORT_DIR" ] && [ -f "$REPORT_DIR/report.json" ]; then + echo "Found report.json at: $REPORT_DIR/report.json" +else + echo "Warning: No versioned report directory found under $REPORTS_DIR" +fi +``` + +**Step 7b: Merge security findings into report.json** + +**Use `jq` for safe JSON manipulation** — do NOT manually rewrite report.json. + +Write the unified findings array to a temporary file, then merge: + +```bash +SECURITY_DIR=".github/modernize/assessment/engines/security" + +# Write the unified findings array +cat > "$SECURITY_DIR/unified-findings.json" << 'SECURITY_EOF' +[ + ... unified findings array from Step 3 ... +] +SECURITY_EOF + +# Merge into report.json using jq +if [ -n "$REPORT_DIR" ] && [ -f "$REPORT_DIR/report.json" ] && command -v jq &> /dev/null; then + jq --argjson sec "$(cat "$SECURITY_DIR/unified-findings.json")" '. + {"security": $sec}' "$REPORT_DIR/report.json" > "$REPORT_DIR/report.json.tmp" && mv "$REPORT_DIR/report.json.tmp" "$REPORT_DIR/report.json" +elif [ -n "$REPORT_DIR" ] && [ -f "$REPORT_DIR/report.json" ]; then + # Fallback without jq: use python + python3 -c " +import json +report_path = '$REPORT_DIR/report.json' +with open(report_path, 'r') as f: report = json.load(f) +with open('$SECURITY_DIR/unified-findings.json', 'r') as f: security = json.load(f) +report['security'] = security +with open(report_path, 'w') as f: json.dump(report, f, indent=2) +" 2>/dev/null || echo "Warning: Could not merge security into report.json (jq and python3 unavailable)" +else + echo "Warning: report.json not found — skipping merge. The standalone security-assessment.json is still valid." +fi + +# Clean up temp file +rm -f "$SECURITY_DIR/unified-findings.json" +``` + +**Step 7c: Handle missing report.json** + +If no `report-*` directory or `report.json` exists (e.g., AppCAT was not run or failed), create a minimal stub in a new versioned directory: + +```bash +if [ -z "$REPORT_DIR" ] || [ ! -f "$REPORT_DIR/report.json" ]; then + REPORT_ID=$(date -u +"%Y%m%d%H%M%S") + REPORT_DIR="$REPORTS_DIR/report-$REPORT_ID" + mkdir -p "$REPORT_DIR" + cat > "$REPORT_DIR/report.json" << STUB_EOF +{ + "metadata": { + "id": "$REPORT_ID", + "generated_at": "$(date -u +"%Y-%m-%dT%H:%M:%S.0000000Z")", + "note": "Security-only report (AppCAT was not run or failed)" + }, + "security": $(cat "$SECURITY_DIR/unified-findings.json" 2>/dev/null || echo "[]") +} +STUB_EOF + echo "Created stub report at: $REPORT_DIR/report.json" +fi +``` + +## Error Handling + +- If no security result files exist at all, write empty reports (`security-assessment.json` with zero findings, `security-assessment.md` with "No vulnerabilities found") +- If a CWE result file is malformed, skip it and continue with the rest +- If the `report.json` merge fails, log a warning but do NOT fail the overall task — the standalone `security-assessment.json` is still valid +- Always produce all three output files (`security-assessment.json`, `security-assessment.md`, and the merged `report.json`) diff --git a/.github/skills/validate-assessment-rulebook-compliance/SKILL.md b/.github/skills/validate-assessment-rulebook-compliance/SKILL.md new file mode 100644 index 000000000..2e03e2581 --- /dev/null +++ b/.github/skills/validate-assessment-rulebook-compliance/SKILL.md @@ -0,0 +1,37 @@ +--- +name: validate-assessment-rulebook-compliance +description: Validate that assessment findings cover rulebook source technologies and patterns +--- + +# Validate Assessment Rulebook Compliance + +Check whether the assessment detected the source technologies and patterns defined in the rulebook. + +## User Input + +- assessment-findings-path (Mandatory): Path to the pre-processed assessment findings JSON file +- compliance-output-path (Mandatory): Path to write the compliance markdown summary + +## Workflow + +1. Read the assessment findings from ${assessment-findings-path}. This is a JSON array of objects with `ruleId`, `title`, and `description` fields representing issues detected during assessment. +2. Read the rulebook files provided as attachments (targets.md, policies.md). +3. Extract each source technology, pattern, or constraint mentioned in the rulebook files. +4. For each rulebook item, determine whether the assessment findings contain a rule that detects or covers it. Use semantic matching — the rule description does not need to be an exact match, but must clearly relate to the same technology or pattern. +5. Write a markdown summary to ${compliance-output-path} with the following format: + +## Assessment Rulebook Coverage + +| Rulebook | Source Technology/Pattern | Status | Evidence (Rule ID) | +|----------|--------------------------|--------|-------------------| +| targets.md | brief technology/pattern summary | ✅ DETECTED | matching-rule-id | +| policies.md | another pattern | ❌ NOT DETECTED | - | + +**DETECTED: X/Y · NOT DETECTED: Z/Y** + +## Rules + +- Only include source technologies and patterns from the rulebook — do not include target recommendations. +- A rulebook item is DETECTED if at least one assessment rule clearly relates to detecting that technology or pattern. +- If multiple rules match, list the most specific one. +- Only write the markdown file, do not modify any other files. diff --git a/.github/skills/validate-rulebook-compliance/SKILL.md b/.github/skills/validate-rulebook-compliance/SKILL.md new file mode 100644 index 000000000..3b64f635d --- /dev/null +++ b/.github/skills/validate-rulebook-compliance/SKILL.md @@ -0,0 +1,31 @@ +--- +name: validate-rulebook-compliance +description: Validate rulebook compliance by mapping rulebook rules to plan tasks +--- + +# Validate Rulebook Compliance + +Map each rulebook rule to the tasks in the modernization plan and produce a compliance summary. + +## User Input + +- tasks-json-path (Mandatory): Path to the tasks.json file +- compliance-output-path (Mandatory): Path to write the compliance markdown summary + +## Workflow + +1. Read the tasks from ${tasks-json-path} +2. Read the rulebook files provided as attachments +3. For each rulebook rule, determine which task (if any) addresses it +4. Write a markdown summary to ${compliance-output-path} with the following format: + +## Rulebook Compliance + +| Rulebook | Rule | Status | Task | +|----------|------|--------|------| +| targets.md | brief rule summary | ✅ COVERED | 001 | +| policies.md | another rule | ❌ NOT COVERED | - | + +**COVERED: X/Y · NOT COVERED: Z/Y** + +Include all rules from all rulebook files. Only write the markdown file, do not modify any other files. diff --git a/.github/skills/validate-rulebook-evidence/SKILL.md b/.github/skills/validate-rulebook-evidence/SKILL.md new file mode 100644 index 000000000..624576d69 --- /dev/null +++ b/.github/skills/validate-rulebook-evidence/SKILL.md @@ -0,0 +1,32 @@ +--- +name: validate-rulebook-evidence +description: Analyze code changes and produce rulebook compliance evidence from git diff +--- + +# Validate Rulebook Evidence + +Analyze the git diff and produce rulebook compliance evidence showing which rules were implemented. + +## User Input + +- baseline-commit-sha (Mandatory): The baseline commit SHA to diff from +- rulebook-file-list (Mandatory): Comma-separated list of rulebook file names +- evidence-output-path (Mandatory): Path to write the evidence markdown summary + +## Workflow + +1. Analyze the git diff from baseline commit ${baseline-commit-sha} to HEAD +2. The rulebook files are: ${rulebook-file-list} +3. Write a markdown summary to ${evidence-output-path} with the following format: + +## Rulebook Code Evidence (from diff) + +### [policies.md] +**Rule: brief rule summary** +```diff ++ filepath:line added code +- filepath:line removed code +``` + +Focus on the 2-5 most impactful rules. Each diff line starts with '+' or '-' and includes file:line prefix. +Only write the markdown file, do not modify any other files. From ded82b296d1e2bc8e83016304f5b91097af0b123 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 May 2026 06:08:43 +0000 Subject: [PATCH 2/7] Initial plan From 1f39c0161ce296f46434fb4cd35198a5e7f1c2ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 May 2026 06:18:34 +0000 Subject: [PATCH 3/7] Add assessment report artifacts Agent-Logs-Url: https://github.com/qianwens/PhotoAlbum-Java/sessions/82480c71-f7c9-403f-bbf2-d2fbf546a091 Co-authored-by: qianwens <37290631+qianwens@users.noreply.github.com> --- .../facts/api-service-contracts.md | 77 + .../facts/architecture-diagram.md | 126 ++ .../facts/assessment-overview.md | 10 + .../facts/business-workflows.md | 91 ++ .../facts/configuration-inventory.md | 114 ++ .../facts/data-architecture.md | 63 + .../facts/dependency-map.md | 69 + .../reports/report-20260521061104/report.html | 1378 ++++++++++++++++ .../reports/report-20260521061104/report.json | 1021 ++++++++++++ .../reports/report-20260521061104/report.md | 137 ++ .github/modernize/ccacontext/.ccaskills | 68 - .github/modernize/ccacontext/cleanup.sh | 50 - .github/skills/api-service-contracts/SKILL.md | 210 --- .github/skills/architecture-diagram/SKILL.md | 190 --- .github/skills/assessment/SKILL.md | 173 -- .github/skills/business-workflows/SKILL.md | 199 --- .../skills/configuration-inventory/SKILL.md | 205 --- .../create-dotnet-upgrade-plan/SKILL.md | 143 -- .../dotnet-upgrade-guideline.md | 43 - .../tasks-schema.json | 349 ---- .../upgrade-plan-template.md | 42 - .../skills/create-java-upgrade-plan/SKILL.md | 58 - .../java-upgrade-guideline.md | 44 - .../tasks-schema.json | 349 ---- .../upgrade-plan-template.md | 51 - .../skills/create-modernization-plan/SKILL.md | 129 -- .../dotnet-upgrade-guideline.md | 43 - .../infra-plan-template.md | 48 - .../java-upgrade-guideline.md | 44 - .../plan-template.md | 70 - .../questionnaire.md | 19 - .../security-plan-template.md | 26 - .../supported-patterns-dotnet.md | 50 - .../supported-patterns-java.md | 88 - .../tasks-schema.json | 349 ---- .../skills/cve-known-vulnerabilities/SKILL.md | 201 --- .github/skills/cwe-code-quality/SKILL.md | 153 -- .../cwe-concurrency-synchronization/SKILL.md | 113 -- .../skills/cwe-credentials-secrets/SKILL.md | 109 -- .../skills/cwe-file-path-security/SKILL.md | 109 -- .github/skills/cwe-injection-attacks/SKILL.md | 137 -- .github/skills/cwe-memory-safety/SKILL.md | 149 -- .github/skills/data-architecture/SKILL.md | 227 --- .github/skills/dependency-map/SKILL.md | 180 --- .../skills/dotnet-upgrade-assessment/SKILL.md | 149 -- .../skills/execute-java-upgrade-task/SKILL.md | 431 ----- .../execute-modernization-task/SKILL.md | 53 - .github/skills/execute-upgrade-task/SKILL.md | 23 - .github/skills/fact-application-name/SKILL.md | 112 -- .github/skills/fact-application-port/SKILL.md | 96 -- .github/skills/fact-application-type/SKILL.md | 133 -- .../skills/fact-architecture-pattern/SKILL.md | 218 --- .github/skills/fact-base-image/SKILL.md | 102 -- .../fact-communication-protocols/SKILL.md | 118 -- .../fact-compliance-requirements/SKILL.md | 131 -- .github/skills/fact-container-engine/SKILL.md | 108 -- .../skills/fact-container-version/SKILL.md | 99 -- .../skills/fact-data-classification/SKILL.md | 151 -- .../fact-embedded-language-usage/SKILL.md | 277 ---- .../fact-environment-variables/SKILL.md | 105 -- .../fact-external-dependencies/SKILL.md | 124 -- .../skills/fact-external-services/SKILL.md | 93 -- .../fact-hardware-requirements/SKILL.md | 115 -- .github/skills/fact-health-checks/SKILL.md | 110 -- .github/skills/fact-image-layers/SKILL.md | 102 -- .github/skills/fact-image-size/SKILL.md | 121 -- .../fact-language-dependencies/SKILL.md | 104 -- .../fact-licensing-information/SKILL.md | 119 -- .../skills/fact-multi-stage-build/SKILL.md | 92 -- .github/skills/fact-network-settings/SKILL.md | 90 -- .github/skills/fact-operating-system/SKILL.md | 126 -- .../skills/fact-orchestration-tool/SKILL.md | 96 -- .github/skills/fact-profile-settings/SKILL.md | 332 ---- .github/skills/fact-resource-limits/SKILL.md | 103 -- .../skills/fact-runtime-environment/SKILL.md | 107 -- .../fact-security-implementation/SKILL.md | 142 -- .../skills/fact-service-definition/SKILL.md | 108 -- .../skills/fact-servlet-container/SKILL.md | 310 ---- .../fact-startup-instrumentation/SKILL.md | 371 ----- .github/skills/fact-system-packages/SKILL.md | 99 -- .../skills/fact-testing-framework/SKILL.md | 139 -- .../skills/fact-version-information/SKILL.md | 109 -- .github/skills/fact-volume-mounts/SKILL.md | 96 -- .github/skills/fact-xml-configs/SKILL.md | 298 ---- .github/skills/generate-report-html/SKILL.md | 54 - .../generate-report-html.py | 1410 ----------------- .github/skills/generate-report-md/SKILL.md | 54 - .../generate-report-md/generate-report-md.py | 579 ------- .github/skills/integration-tests/SKILL.md | 324 ---- .../references/azure-auth-strategies.md | 189 --- .../azure-servicebus-testcontainers.md | 688 -------- .../azure-storage-testcontainers.md | 276 ---- .../references/layer1-local-integration.md | 358 ----- .../layer2-runner-script-templates.md | 287 ---- .../references/layer2-smoke-tests.md | 261 --- .../references/layer3-azure-integration.md | 194 --- .../layer4-behavioral-comparison.md | 242 --- .github/skills/rearchitect/SKILL.md | 126 -- .../skills/reconcile-tasks-with-plan/SKILL.md | 46 - .../repository-dependency-graph/SKILL.md | 250 --- .github/skills/rulebook-create/SKILL.md | 89 -- .../rulebook-create/charter-template.md | 36 - .../rulebook-create/policies-template.md | 70 - .../references/charter-default.md | 59 - .../references/policies-default.md | 123 -- .../references/targets-default.md | 73 - .../rulebook-create/targets-template.md | 35 - .../skills/security-assessment-merge/SKILL.md | 318 ---- .../SKILL.md | 37 - .../validate-rulebook-compliance/SKILL.md | 31 - .../validate-rulebook-evidence/SKILL.md | 32 - 111 files changed, 3086 insertions(+), 16471 deletions(-) create mode 100644 .github/modernize/assessment/reports/report-20260521061104/facts/api-service-contracts.md create mode 100644 .github/modernize/assessment/reports/report-20260521061104/facts/architecture-diagram.md create mode 100644 .github/modernize/assessment/reports/report-20260521061104/facts/assessment-overview.md create mode 100644 .github/modernize/assessment/reports/report-20260521061104/facts/business-workflows.md create mode 100644 .github/modernize/assessment/reports/report-20260521061104/facts/configuration-inventory.md create mode 100644 .github/modernize/assessment/reports/report-20260521061104/facts/data-architecture.md create mode 100644 .github/modernize/assessment/reports/report-20260521061104/facts/dependency-map.md create mode 100644 .github/modernize/assessment/reports/report-20260521061104/report.html create mode 100644 .github/modernize/assessment/reports/report-20260521061104/report.json create mode 100644 .github/modernize/assessment/reports/report-20260521061104/report.md delete mode 100644 .github/modernize/ccacontext/.ccaskills delete mode 100644 .github/modernize/ccacontext/cleanup.sh delete mode 100644 .github/skills/api-service-contracts/SKILL.md delete mode 100644 .github/skills/architecture-diagram/SKILL.md delete mode 100644 .github/skills/assessment/SKILL.md delete mode 100644 .github/skills/business-workflows/SKILL.md delete mode 100644 .github/skills/configuration-inventory/SKILL.md delete mode 100644 .github/skills/create-dotnet-upgrade-plan/SKILL.md delete mode 100644 .github/skills/create-dotnet-upgrade-plan/dotnet-upgrade-guideline.md delete mode 100644 .github/skills/create-dotnet-upgrade-plan/tasks-schema.json delete mode 100644 .github/skills/create-dotnet-upgrade-plan/upgrade-plan-template.md delete mode 100644 .github/skills/create-java-upgrade-plan/SKILL.md delete mode 100644 .github/skills/create-java-upgrade-plan/java-upgrade-guideline.md delete mode 100644 .github/skills/create-java-upgrade-plan/tasks-schema.json delete mode 100644 .github/skills/create-java-upgrade-plan/upgrade-plan-template.md delete mode 100644 .github/skills/create-modernization-plan/SKILL.md delete mode 100644 .github/skills/create-modernization-plan/dotnet-upgrade-guideline.md delete mode 100644 .github/skills/create-modernization-plan/infra-plan-template.md delete mode 100644 .github/skills/create-modernization-plan/java-upgrade-guideline.md delete mode 100644 .github/skills/create-modernization-plan/plan-template.md delete mode 100644 .github/skills/create-modernization-plan/questionnaire.md delete mode 100644 .github/skills/create-modernization-plan/security-plan-template.md delete mode 100644 .github/skills/create-modernization-plan/supported-patterns-dotnet.md delete mode 100644 .github/skills/create-modernization-plan/supported-patterns-java.md delete mode 100644 .github/skills/create-modernization-plan/tasks-schema.json delete mode 100644 .github/skills/cve-known-vulnerabilities/SKILL.md delete mode 100644 .github/skills/cwe-code-quality/SKILL.md delete mode 100644 .github/skills/cwe-concurrency-synchronization/SKILL.md delete mode 100644 .github/skills/cwe-credentials-secrets/SKILL.md delete mode 100644 .github/skills/cwe-file-path-security/SKILL.md delete mode 100644 .github/skills/cwe-injection-attacks/SKILL.md delete mode 100644 .github/skills/cwe-memory-safety/SKILL.md delete mode 100644 .github/skills/data-architecture/SKILL.md delete mode 100644 .github/skills/dependency-map/SKILL.md delete mode 100644 .github/skills/dotnet-upgrade-assessment/SKILL.md delete mode 100644 .github/skills/execute-java-upgrade-task/SKILL.md delete mode 100644 .github/skills/execute-modernization-task/SKILL.md delete mode 100644 .github/skills/execute-upgrade-task/SKILL.md delete mode 100644 .github/skills/fact-application-name/SKILL.md delete mode 100644 .github/skills/fact-application-port/SKILL.md delete mode 100644 .github/skills/fact-application-type/SKILL.md delete mode 100644 .github/skills/fact-architecture-pattern/SKILL.md delete mode 100644 .github/skills/fact-base-image/SKILL.md delete mode 100644 .github/skills/fact-communication-protocols/SKILL.md delete mode 100644 .github/skills/fact-compliance-requirements/SKILL.md delete mode 100644 .github/skills/fact-container-engine/SKILL.md delete mode 100644 .github/skills/fact-container-version/SKILL.md delete mode 100644 .github/skills/fact-data-classification/SKILL.md delete mode 100644 .github/skills/fact-embedded-language-usage/SKILL.md delete mode 100644 .github/skills/fact-environment-variables/SKILL.md delete mode 100644 .github/skills/fact-external-dependencies/SKILL.md delete mode 100644 .github/skills/fact-external-services/SKILL.md delete mode 100644 .github/skills/fact-hardware-requirements/SKILL.md delete mode 100644 .github/skills/fact-health-checks/SKILL.md delete mode 100644 .github/skills/fact-image-layers/SKILL.md delete mode 100644 .github/skills/fact-image-size/SKILL.md delete mode 100644 .github/skills/fact-language-dependencies/SKILL.md delete mode 100644 .github/skills/fact-licensing-information/SKILL.md delete mode 100644 .github/skills/fact-multi-stage-build/SKILL.md delete mode 100644 .github/skills/fact-network-settings/SKILL.md delete mode 100644 .github/skills/fact-operating-system/SKILL.md delete mode 100644 .github/skills/fact-orchestration-tool/SKILL.md delete mode 100644 .github/skills/fact-profile-settings/SKILL.md delete mode 100644 .github/skills/fact-resource-limits/SKILL.md delete mode 100644 .github/skills/fact-runtime-environment/SKILL.md delete mode 100644 .github/skills/fact-security-implementation/SKILL.md delete mode 100644 .github/skills/fact-service-definition/SKILL.md delete mode 100644 .github/skills/fact-servlet-container/SKILL.md delete mode 100644 .github/skills/fact-startup-instrumentation/SKILL.md delete mode 100644 .github/skills/fact-system-packages/SKILL.md delete mode 100644 .github/skills/fact-testing-framework/SKILL.md delete mode 100644 .github/skills/fact-version-information/SKILL.md delete mode 100644 .github/skills/fact-volume-mounts/SKILL.md delete mode 100644 .github/skills/fact-xml-configs/SKILL.md delete mode 100644 .github/skills/generate-report-html/SKILL.md delete mode 100644 .github/skills/generate-report-html/generate-report-html.py delete mode 100644 .github/skills/generate-report-md/SKILL.md delete mode 100644 .github/skills/generate-report-md/generate-report-md.py delete mode 100644 .github/skills/integration-tests/SKILL.md delete mode 100644 .github/skills/integration-tests/references/azure-auth-strategies.md delete mode 100644 .github/skills/integration-tests/references/azure-servicebus-testcontainers.md delete mode 100644 .github/skills/integration-tests/references/azure-storage-testcontainers.md delete mode 100644 .github/skills/integration-tests/references/layer1-local-integration.md delete mode 100644 .github/skills/integration-tests/references/layer2-runner-script-templates.md delete mode 100644 .github/skills/integration-tests/references/layer2-smoke-tests.md delete mode 100644 .github/skills/integration-tests/references/layer3-azure-integration.md delete mode 100644 .github/skills/integration-tests/references/layer4-behavioral-comparison.md delete mode 100644 .github/skills/rearchitect/SKILL.md delete mode 100644 .github/skills/reconcile-tasks-with-plan/SKILL.md delete mode 100644 .github/skills/repository-dependency-graph/SKILL.md delete mode 100644 .github/skills/rulebook-create/SKILL.md delete mode 100644 .github/skills/rulebook-create/charter-template.md delete mode 100644 .github/skills/rulebook-create/policies-template.md delete mode 100644 .github/skills/rulebook-create/references/charter-default.md delete mode 100644 .github/skills/rulebook-create/references/policies-default.md delete mode 100644 .github/skills/rulebook-create/references/targets-default.md delete mode 100644 .github/skills/rulebook-create/targets-template.md delete mode 100644 .github/skills/security-assessment-merge/SKILL.md delete mode 100644 .github/skills/validate-assessment-rulebook-compliance/SKILL.md delete mode 100644 .github/skills/validate-rulebook-compliance/SKILL.md delete mode 100644 .github/skills/validate-rulebook-evidence/SKILL.md diff --git a/.github/modernize/assessment/reports/report-20260521061104/facts/api-service-contracts.md b/.github/modernize/assessment/reports/report-20260521061104/facts/api-service-contracts.md new file mode 100644 index 000000000..a11b3f0c1 --- /dev/null +++ b/.github/modernize/assessment/reports/report-20260521061104/facts/api-service-contracts.md @@ -0,0 +1,77 @@ +# API & Service Communication Contracts + +The application exposes a small HTTP surface made up of one gallery page, one upload endpoint, one image streaming endpoint, and detail/delete routes for individual photos. Communication is entirely synchronous: browser requests are handled by Spring MVC controllers, which call a local service and persist to Oracle through JPA. + +## Service Catalog + +| Service | Port | Category | Purpose | +|---|---:|---|---| +| `photoalbum-java-app` | 8080 | API Layer | Serves gallery/detail pages, accepts uploads, streams photo content, and deletes photos | +| `oracle-db` | 1521 | Infrastructure | Stores photo metadata and image BLOB data for the web application | + +## API Endpoints Inventory + +| Service | Method | Path | Request Type | Response Type | +|---|---|---|---|---| +| `photoalbum-java-app` | GET | `/` | None | Thymeleaf `index.html` view with `photos` model data | +| `photoalbum-java-app` | POST | `/upload` | Multipart form field `files` containing one or more `MultipartFile` objects | JSON `Map` with `success`, `uploadedPhotos`, and `failedUploads` | +| `photoalbum-java-app` | GET | `/detail/{id}` | Path parameter `id:String` | Thymeleaf `detail.html` view or redirect to `/` when not found | +| `photoalbum-java-app` | POST | `/detail/{id}/delete` | Path parameter `id:String` | Redirect to `/` with flash success/error message | +| `photoalbum-java-app` | GET | `/photo/{id}` | Path parameter `id:String` | `ResponseEntity` image stream with 200, 404, or 500 status | + +## Management & Observability Endpoints + +| Service | Endpoint | Custom Metrics | +|---|---|---| +| `photoalbum-java-app` | None detected | No Actuator, health, metrics, or Swagger endpoints detected | + +## DTOs & Contracts + +The application does not define dedicated request DTO classes for its HTTP surface. Upload requests are accepted as Spring `MultipartFile` collections, while server-rendered views bind directly to the `Photo` domain entity for display purposes. The only explicit contract helper class is `UploadResult`, which is used internally by the service layer to communicate per-file outcomes before the controller converts those results into an ad hoc JSON map. + +No gateway-level aggregation DTOs, OpenAPI specifications, protobuf schemas, or GraphQL schemas were found. JSON serialization relies on Spring Boot's default Jackson configuration through `spring-boot-starter-json`. + +## Communication Patterns + +All communication is synchronous. Browser clients call Spring MVC controllers over HTTP, controllers invoke `PhotoServiceImpl` through direct in-process method calls, and the service uses `PhotoRepository` for synchronous Oracle database access. No asynchronous messaging, background workers, service discovery, API gateway, client-side load balancing, retry policy, circuit breaker, or timeout library was detected. + +API availability depends on the Oracle database container being healthy before the web container starts; Docker Compose enforces this startup dependency with `depends_on` and a database health check. Security posture is minimal: no TLS termination, authentication, authorization rules, or Spring Security configuration were found, so all endpoints are publicly accessible within the deployment environment. + +## Service Technology Matrix + +| Service | Web | Data Access | Discovery | Gateway | Actuator | Cache | Metrics | +|---|---|---|---|---|---|---|---| +| `photoalbum-java-app` | Spring MVC + Thymeleaf | Spring Data JPA / Hibernate | None | None | None | None | None | +| `oracle-db` | N/A | Oracle database engine | None | None | Health check only | N/A | None | + +## Service Communication Sequence + +```mermaid +sequenceDiagram + participant Browser + participant Home as "HomeController" + participant Service as "PhotoServiceImpl" + participant Repo as "PhotoRepository" + participant DB as "Oracle Database" + + Browser->>Home: POST /upload (multipart files) + Home->>Service: uploadPhoto(file) + alt File passes validation + Service->>Service: Validate MIME type and size + Service->>Service: Read bytes and image dimensions + Service->>Repo: save(Photo) + Repo->>DB: INSERT photo metadata and BLOB + DB-->>Repo: Stored row + Repo-->>Service: Persisted Photo + Home->>Service: getPhotoById(photoId) + Service->>Repo: findById(photoId) + Repo->>DB: SELECT photo by id + DB-->>Repo: Photo row + Repo-->>Service: Photo + Service-->>Home: Upload success result + Home-->>Browser: 200 JSON with uploadedPhotos + else Validation or persistence failure + Service-->>Home: UploadResult failure + Home-->>Browser: 200 JSON with failedUploads or 400 for missing files + end +``` diff --git a/.github/modernize/assessment/reports/report-20260521061104/facts/architecture-diagram.md b/.github/modernize/assessment/reports/report-20260521061104/facts/architecture-diagram.md new file mode 100644 index 000000000..cc8654870 --- /dev/null +++ b/.github/modernize/assessment/reports/report-20260521061104/facts/architecture-diagram.md @@ -0,0 +1,126 @@ +# Architecture Diagram + +This document summarizes the Photo Album application's runtime structure and the main component relationships that support photo upload, gallery browsing, image retrieval, and deletion. + +## Application Architecture + +```mermaid +flowchart TD + subgraph Client["Client Layer"] + Browser["Web Browser"] + end + + subgraph App["Application Layer - Spring Boot 2.7"] + Views["Thymeleaf Views"] + UploadJs["Upload JavaScript"] + HomeCtrl["HomeController"] + DetailCtrl["DetailController"] + FileCtrl["PhotoFileController"] + PhotoSvc["PhotoServiceImpl"] + end + + subgraph Data["Data Layer"] + PhotoRepo["Spring Data JPA Repository"] + OracleDB[("Oracle Database PHOTOS table")] + end + + subgraph External["External Services"] + BootstrapCdn["Bootstrap CDN"] + OracleContainer["Oracle DB Container"] + end + + Browser -->|"Loads gallery and detail pages"| Views + Browser -->|"Drag and drop uploads"| UploadJs + Views -->|"GET / and GET /detail/{id}"| HomeCtrl + Views -->|"Photo detail navigation and delete"| DetailCtrl + UploadJs -->|"POST /upload"| HomeCtrl + Browser -->|"GET /photo/{id}"| FileCtrl + HomeCtrl -->|"List and create photo records"| PhotoSvc + DetailCtrl -->|"Lookup, navigate, delete"| PhotoSvc + FileCtrl -->|"Load BLOB content"| PhotoSvc + PhotoSvc -->|"CRUD and custom queries"| PhotoRepo + PhotoRepo -->|"SQL and BLOB persistence"| OracleDB + OracleContainer -->|"Hosts"| OracleDB + Browser -->|"Fetches UI assets"| BootstrapCdn +``` + +### Technology Stack Summary + +| Layer | Technology | Version | Purpose | +|---|---|---|---| +| Presentation | Thymeleaf templates, Bootstrap, vanilla JavaScript | Thymeleaf via Spring Boot 2.7.18, Bootstrap 5.3.0 | Renders gallery/detail pages and handles drag-and-drop uploads | +| Web | Spring Boot Web, Spring MVC | 2.7.18 | Handles HTTP requests and server-side page composition | +| Business Logic | PhotoServiceImpl | Application code | Validates uploads, extracts metadata, coordinates persistence | +| Data Access | Spring Data JPA, Hibernate | Spring Boot managed | Maps the Photo entity to Oracle and executes repository queries | +| Storage | Oracle Database Free/XE | Container image latest / XE-compatible JDBC URL | Stores photo metadata and image BLOB data | +| Delivery | Docker, Docker Compose | Dockerfile + compose | Runs the web app and Oracle database as containers | + +### Data Storage & External Services + +The application stores all photo metadata and binary image content in a single Oracle database table named `PHOTOS`. Beyond the database, the only notable external dependency at runtime is the Bootstrap CDN used by the server-rendered UI for styling and client-side bundle delivery. + +### Key Architectural Decisions + +- Uses a classic layered Spring MVC design where controllers delegate all business logic and persistence orchestration to a single `PhotoServiceImpl` service. +- Persists uploaded images as Oracle BLOB data instead of filesystem storage, which keeps the application stateless from a file-hosting perspective. +- Uses server-rendered Thymeleaf pages for navigation while enhancing uploads with client-side JavaScript for drag-and-drop and immediate gallery updates. + +## Component Relationships + +```mermaid +flowchart LR + subgraph Presentation["Presentation"] + IndexView["index.html"] + DetailView["detail.html"] + UploadClient["upload.js"] + HomeControllerNode["HomeController"] + DetailControllerNode["DetailController"] + PhotoFileControllerNode["PhotoFileController"] + end + + subgraph Business["Business Logic"] + PhotoServiceNode["PhotoService"] + PhotoServiceImplNode["PhotoServiceImpl"] + UploadResultNode["UploadResult"] + end + + subgraph DataAccess["Data Access"] + PhotoRepoNode["PhotoRepository"] + PhotoEntityNode["Photo Entity"] + end + + subgraph Infra["Infrastructure"] + SpringBootNode["Spring Boot Runtime"] + TxNode["Transactional Boundary"] + OracleNode["Oracle Database"] + end + + IndexView -->|"submits uploads to"| UploadClient + IndexView -->|"binds model data from"| HomeControllerNode + DetailView -->|"binds model data from"| DetailControllerNode + UploadClient -->|"calls"| HomeControllerNode + HomeControllerNode -->|"delegates"| PhotoServiceNode + DetailControllerNode -->|"delegates"| PhotoServiceNode + PhotoFileControllerNode -->|"delegates"| PhotoServiceNode + PhotoServiceNode -->|"implemented by"| PhotoServiceImplNode + PhotoServiceImplNode -->|"returns"| UploadResultNode + PhotoServiceImplNode -->|"queries and saves"| PhotoRepoNode + PhotoRepoNode -->|"maps"| PhotoEntityNode + PhotoRepoNode -->|"executes against"| OracleNode + SpringBootNode -.->|"creates and wires"| Presentation + TxNode -.->|"wraps service methods"| PhotoServiceImplNode +``` + +### Component Inventory + +| Component | Layer | Type | Responsibility | +|---|---|---|---| +| `index.html` | Presentation | Thymeleaf template | Displays the gallery, upload area, and flash/error messages | +| `detail.html` | Presentation | Thymeleaf template | Shows a single photo, metadata, navigation, and delete action | +| `upload.js` | Presentation | Browser script | Validates dropped files, posts multipart uploads, and prepends new cards to the gallery | +| `HomeController` | Presentation | MVC controller | Handles gallery rendering and upload responses | +| `DetailController` | Presentation | MVC controller | Handles detail page rendering and delete operations | +| `PhotoFileController` | Presentation | MVC controller | Streams image bytes with cache-control headers | +| `PhotoServiceImpl` | Business Logic | Service | Applies upload rules, extracts image dimensions, and coordinates CRUD operations | +| `PhotoRepository` | Data Access | Spring Data repository | Executes Oracle-specific native SQL for ordered listing and navigation | +| `Photo` | Data Access | JPA entity | Represents persisted photo metadata and BLOB content | diff --git a/.github/modernize/assessment/reports/report-20260521061104/facts/assessment-overview.md b/.github/modernize/assessment/reports/report-20260521061104/facts/assessment-overview.md new file mode 100644 index 000000000..58bd3ba47 --- /dev/null +++ b/.github/modernize/assessment/reports/report-20260521061104/facts/assessment-overview.md @@ -0,0 +1,10 @@ +# Assessment Overview + +This directory contains supplementary assessment documents generated from the repository analysis. Use the links below to navigate to the detailed fact files that were copied into this versioned report. + +- [architecture-diagram.md](architecture-diagram.md) — High-level application architecture and detailed component relationship diagrams. +- [dependency-map.md](dependency-map.md) — Declared build dependencies grouped by functional category, including test dependencies. +- [api-service-contracts.md](api-service-contracts.md) — Service catalog, endpoint inventory, communication patterns, and API sequence flow. +- [data-architecture.md](data-architecture.md) — Database configuration, entity model, repository methods, and data ownership boundaries. +- [configuration-inventory.md](configuration-inventory.md) — Configuration sources, runtime profiles, properties, secrets handling, and runtime settings. +- [business-workflows.md](business-workflows.md) — Core domain entities, user workflows, business rules, and workflow sequence diagram. diff --git a/.github/modernize/assessment/reports/report-20260521061104/facts/business-workflows.md b/.github/modernize/assessment/reports/report-20260521061104/facts/business-workflows.md new file mode 100644 index 000000000..cc3b663ef --- /dev/null +++ b/.github/modernize/assessment/reports/report-20260521061104/facts/business-workflows.md @@ -0,0 +1,91 @@ +# Core Business Workflows + +The Photo Album application supports a simple user journey: upload image files, browse the gallery, inspect photo details, and remove photos when no longer needed. Its business logic is concentrated around validating upload eligibility, preserving metadata, and presenting stored photos in a user-friendly order. + +## Domain Entities + +| Entity | Service / Bounded Context | Description | Key Relationships | +|---|---|---|---| +| `Photo` | Photo Management | Core business record representing a user-uploaded image plus its display metadata | Serves as the source of truth for gallery cards, detail views, navigation, and deletion | +| `UploadResult` | Photo Management | Transient workflow object describing whether an individual file upload succeeded or failed | Produced during upload processing and converted into the JSON response payload | + +## Service-to-Domain Mapping + +| Service | Domain Context | Owned Entities | External Dependencies | +|---|---|---|---| +| `photoalbum-java-app` | Photo Management | `Photo`, `UploadResult` | Oracle database for persistence; browser clients for UI-driven workflows | +| `oracle-db` | Persistence Infrastructure | Physical storage for `Photo` records | None beyond its role as the backing data store | + +## Primary Workflows + +### Workflow 1: Upload Photos + +A user drags photos into the gallery page or selects them through the file picker. The browser script performs a first pass of MIME type and file size validation, then posts the accepted files to `/upload`. The service layer validates each file again, rejects unsupported or empty uploads, extracts image dimensions when possible, assigns a UUID-based stored filename for compatibility, saves the photo bytes and metadata, and returns a per-file success or failure outcome. + +Business rules involved: +- Only JPEG, PNG, GIF, and WebP files are accepted. +- Each file must be non-empty and no larger than 10 MB. +- Upload success is reported per file so mixed-result batches can still partially succeed. +- Image dimension extraction is best-effort; failure to read dimensions does not block persistence. + +### Workflow 2: Browse Gallery and View Photo Details + +When a user opens `/`, the application retrieves all photos ordered by most recent upload time and renders them as gallery cards. Selecting a card opens `/detail/{id}`, where the application loads the chosen photo, computes previous/next navigation targets based on upload timestamps, and displays metadata such as file size, MIME type, and image dimensions. + +Business rules involved: +- Gallery ordering is newest-first. +- Missing photo identifiers gracefully redirect the user back to the gallery. +- Previous and next navigation is derived from upload time, not filename or insertion order. + +### Workflow 3: Delete a Photo + +From the detail page, a user can submit the delete action for a specific photo. The service checks whether the record exists, removes it from the database when present, and redirects the user back to the gallery with a success or error flash message. + +Business rules involved: +- Deletion only succeeds when the target photo exists. +- The UI always returns the user to the gallery after the operation. +- User confirmation is required in the browser before the delete form is submitted. + +## Cross-Service Data Flows + +There is no multi-service business composition layer in this application. Business data flows from the browser to the Spring Boot application and then to Oracle, with the same service acting as the source of truth for validation, persistence, and presentation. When failures occur, the application degrades in business-visible ways: upload batches return per-file error messages, missing detail records redirect to the gallery, and delete attempts surface a user-facing error message instead of throwing a visible exception. + +## Business Workflow Sequence + +```mermaid +sequenceDiagram + participant User + participant Browser as "Gallery UI" + participant Home as "HomeController" + participant Service as "PhotoServiceImpl" + participant Repo as "PhotoRepository" + participant DB as "Oracle Database" + + User->>Browser: Drop image files into upload zone + Browser->>Browser: Validate file type and size client-side + Browser->>Home: Submit accepted files to /upload + Home->>Service: Process each file + Service->>Service: Enforce upload rules and extract metadata + alt File is valid + Service->>Repo: Save Photo record + Repo->>DB: Persist metadata and image BLOB + DB-->>Repo: Stored photo + Repo-->>Service: Persisted entity + Service-->>Home: Success with photo id + Home-->>Browser: JSON response with uploaded photo summary + Browser-->>User: Show success and prepend new gallery card + else File is invalid or save fails + Service-->>Home: Failure result with error message + Home-->>Browser: JSON response with failedUploads entry + Browser-->>User: Show upload error message + end +``` + +## Business Rules & Decision Logic + +- **Validation rules:** The application accepts only JPEG, PNG, GIF, and WebP uploads, enforces a 10 MB per-file limit, and rejects empty files. +- **Decision logic:** Upload processing follows a per-file success/failure model so one bad file does not block the entire batch. +- **Computed values:** The service derives image width and height from image bytes when available, and the UI computes human-readable file size displays and formatted upload timestamps. +- **State transitions:** A `Photo` record moves from not persisted to persisted on upload and from persisted to removed on delete; there are no intermediate approval states. +- **Transactions:** `PhotoServiceImpl` is annotated with `@Transactional`, so persistence operations run inside Spring-managed transaction boundaries. +- **Error handling and authorization:** The application logs operational failures and converts them into redirects or structured upload errors. No business-level authorization rules or role checks were detected. diff --git a/.github/modernize/assessment/reports/report-20260521061104/facts/configuration-inventory.md b/.github/modernize/assessment/reports/report-20260521061104/facts/configuration-inventory.md new file mode 100644 index 000000000..ca5ec1136 --- /dev/null +++ b/.github/modernize/assessment/reports/report-20260521061104/facts/configuration-inventory.md @@ -0,0 +1,114 @@ +# Configuration & Externalized Settings Inventory + +The application uses a small set of local configuration sources: Spring property files, Docker Compose service definitions, Docker image settings, and Oracle initialization scripts. Configuration is straightforward, but secrets are stored inline in repository-managed files rather than external secret stores. + +## Configuration Sources + +| Source | Type | Path/Location | Notes | +|---|---|---|---| +| Spring application properties | Runtime config | `src/main/resources/application.properties` | Default server, datasource, JPA, upload, and logging settings | +| Spring profile properties | Runtime config | `src/main/resources/application-docker.properties` | Docker-specific Oracle connection and logging overrides | +| Docker Compose | Container orchestration config | `docker-compose.yml` | Defines web and Oracle services, environment variables, networking, and startup order | +| Dockerfile | Image/runtime config | `Dockerfile` | Defines build image, runtime image, JVM options, and exposed port | +| Maven build file | Build config | `pom.xml` | Declares framework versions and dependencies | +| Oracle init SQL | Database bootstrap config | `oracle-init/*.sql` | Creates database user, verifies privileges, and supports health checks | +| Test annotation config | Test runtime signal | `src/test/java/com/photoalbum/PhotoAlbumApplicationTests.java` | Activates the `test` Spring profile during context-load testing | + +## Build Profiles + +| Profile | Activation | Purpose | Key Dependencies/Plugins | +|---|---|---|---| +| Default Maven build | Automatic | Standard packaging of the Spring Boot jar | `spring-boot-maven-plugin`; no explicit Maven `` detected | + +## Runtime Profiles + +| Profile | Activation Method | Config Files | Key Overrides | +|---|---|---|---| +| `default` | Automatic when no profile is set | `application.properties` | Oracle `FREEPDB1` JDBC URL, debug logging for app and Spring Web, upload limits | +| `docker` | `SPRING_PROFILES_ACTIVE=docker` in Docker Compose | `application.properties`, `application-docker.properties` | Oracle XE JDBC URL, lower web logging verbosity, container-oriented datasource host | +| `test` | `@ActiveProfiles("test")` on test class | Base properties plus Spring Boot test defaults | Uses test-scoped H2 dependency; no dedicated `application-test.properties` file detected | + +## Properties Inventory + +### photoalbum-java-app + +| Property Key | Default | Profiles | Source | +|---|---|---|---| +| `server.port` | `8080` | default, docker | Spring properties files | +| `server.servlet.encoding.charset` | `UTF-8` | default, docker | Spring properties files | +| `server.servlet.encoding.enabled` | `true` | default, docker | Spring properties files | +| `server.servlet.encoding.force` | `true` | default, docker | Spring properties files | +| `spring.datasource.url` | Oracle JDBC URL | default, docker | Spring properties files | +| `spring.datasource.username` | `photoalbum` | default, docker | Spring properties files | +| `spring.datasource.password` | `[MASKED]` | default, docker | Spring properties files | +| `spring.datasource.driver-class-name` | `oracle.jdbc.OracleDriver` | default, docker | Spring properties files | +| `spring.jpa.database-platform` | `org.hibernate.dialect.OracleDialect` | default, docker | Spring properties files | +| `spring.jpa.hibernate.ddl-auto` | `create` | default, docker | Spring properties files | +| `spring.jpa.show-sql` | `true` | default, docker | Spring properties files | +| `spring.jpa.properties.hibernate.format_sql` | `true` | default, docker | Spring properties files | +| `spring.servlet.multipart.max-file-size` | `10MB` | default, docker | Spring properties files | +| `spring.servlet.multipart.max-request-size` | `50MB` | default, docker | Spring properties files | +| `app.file-upload.max-file-size-bytes` | `10485760` | default, docker | Spring properties files | +| `app.file-upload.allowed-mime-types` | `image/jpeg,image/png,image/gif,image/webp` | default, docker | Spring properties files | +| `app.file-upload.max-files-per-upload` | `10` | default, docker | Spring properties files | +| `logging.level.com.photoalbum` | `DEBUG` or `INFO` | default, docker | Spring properties files | +| `logging.level.org.springframework.web` | `DEBUG` or `WARN` | default, docker | Spring properties files | +| `logging.level.org.hibernate.SQL` | `DEBUG` | docker only | `application-docker.properties` | + +### docker-compose service environment overrides + +| Property Key | Default | Profiles | Source | +|---|---|---|---| +| `SPRING_PROFILES_ACTIVE` | `docker` | docker | `docker-compose.yml` | +| `SPRING_DATASOURCE_URL` | Oracle JDBC URL | docker | `docker-compose.yml` | +| `SPRING_DATASOURCE_USERNAME` | `photoalbum` | docker | `docker-compose.yml` | +| `SPRING_DATASOURCE_PASSWORD` | `[MASKED]` | docker | `docker-compose.yml` | +| `ORACLE_PASSWORD` | `[MASKED]` | docker | `docker-compose.yml` | +| `APP_USER` | `photoalbum` | docker | `docker-compose.yml` | +| `APP_USER_PASSWORD` | `[MASKED]` | docker | `docker-compose.yml` | + +## Startup Parameters & Resource Requirements + +| Service | JVM/Runtime Options | Memory | Instance Count | +|---|---|---|---:| +| `photoalbum-java-app` | `JAVA_OPTS="-Xmx512m -Xms256m"`; exposes port 8080 | 256 MB initial heap, 512 MB max heap | 1 container in Compose | +| `oracle-db` | No JVM options; Oracle container health check script | No explicit memory limit configured | 1 container in Compose | + +## Startup Dependency Chain + +1. `oracle-db` starts first and initializes the database user via scripts mounted from `oracle-init/`. +2. Docker Compose waits for the Oracle container health check (`healthcheck.sh`) to report healthy. +3. `photoalbum-java-app` starts only after `oracle-db` is healthy because of `depends_on.condition: service_healthy`. +4. The Spring Boot application then connects to Oracle using the active datasource settings and creates the schema on startup. + +## Secrets & Sensitive Configuration + +| Secret Reference | Type | Storage (masked) | +|---|---|---| +| `spring.datasource.password` | Database password | Inline Spring properties value masked in report | +| `SPRING_DATASOURCE_PASSWORD` | Database password | Docker Compose environment variable masked in report | +| `ORACLE_PASSWORD` | Oracle system/admin password | Docker Compose environment variable masked in report | +| `APP_USER_PASSWORD` | Oracle application user password | Docker Compose environment variable masked in report | + +### Secrets Provisioning Workflow + +Secrets are provisioned locally and statically. Source values are checked into repository-managed Spring properties and Docker Compose environment variables, then injected directly into the application container and Oracle container at startup. No managed identity, external vault, RBAC-governed secret store, or deployment-time secret binding workflow was detected. + +## Feature Flags + +| Flag Name | Default | Controlled By | +|---|---|---| +| None detected | N/A | No `@ConditionalOnProperty`, feature flag framework, or toggle file detected | + +## Framework & Runtime Versions + +| Component | Version | Source | +|---|---|---| +| Spring Boot | `2.7.18` | `pom.xml` parent POM | +| Java target | `1.8` | `pom.xml` properties | +| Maven build image | `3.9.6-eclipse-temurin-8` | `Dockerfile` | +| Java runtime image | `eclipse-temurin:8-jre` | `Dockerfile` | +| Bootstrap | `5.3.0` | CDN reference in Thymeleaf templates | +| Oracle JDBC | `ojdbc8` (managed) | `pom.xml` dependency | +| Oracle database container | `gvenzl/oracle-free:latest` | `docker-compose.yml` | +| Spring Data JPA / Hibernate | Spring Boot managed | `pom.xml` dependency set | diff --git a/.github/modernize/assessment/reports/report-20260521061104/facts/data-architecture.md b/.github/modernize/assessment/reports/report-20260521061104/facts/data-architecture.md new file mode 100644 index 000000000..1466ea917 --- /dev/null +++ b/.github/modernize/assessment/reports/report-20260521061104/facts/data-architecture.md @@ -0,0 +1,63 @@ +# Data Architecture & Persistence Layer + +The data layer is intentionally small: a single JPA entity is stored in Oracle using Spring Data JPA and Hibernate. All persistence concerns are concentrated in one repository interface, with no caching layer or separate reporting store. + +## Database Configuration + +| Service/Module | DB Type | Profile | Driver | Connection | Migration Tool | +|---|---|---|---|---|---| +| `photoalbum-java-app` | Oracle Database | default | Oracle JDBC (`ojdbc8`) | JDBC URL points to `oracle-db:1521/FREEPDB1` | None; Hibernate creates schema at startup | +| `photoalbum-java-app` | Oracle Database XE | `docker` | Oracle JDBC (`ojdbc8`) | JDBC URL points to `oracle-db:1521:XE` | None; Hibernate creates schema at startup | +| `photoalbum-java-app` tests | H2 in-memory database | `test` | H2 | Spring Boot test auto-configuration with `@ActiveProfiles("test")` | None detected | + +## Data Ownership per Service + +| Service | Tables Owned | ORM Framework | Caching | Notes | +|---|---|---|---|---| +| `photoalbum-java-app` | `PHOTOS` | JPA / Hibernate via Spring Data JPA | None detected | Single-service ownership; stores both metadata and binary photo content | +| `oracle-db` | `PHOTOS` physical storage | Oracle engine | N/A | Backing store only; no independent domain ownership | + +## Entity Model + +```mermaid +erDiagram + Photo { + string id PK + string originalFileName + bytes photoData + string storedFileName + string filePath + long fileSize + string mimeType + datetime uploadedAt + int width + int height + } +``` + +The source entity model is defined in `src/main/java/com/photoalbum/model/Photo.java`. No JPA relationships to other entities were found; the model is a self-contained aggregate for photo content and metadata. + +## Key Repository Methods + +| Service | Repository | Notable Methods | Purpose | +|---|---|---|---| +| `photoalbum-java-app` | `PhotoRepository` (`src/main/java/com/photoalbum/repository/PhotoRepository.java`) | `findAllOrderByUploadedAtDesc()` | Returns gallery items ordered newest-first | +| `photoalbum-java-app` | `PhotoRepository` | `findPhotosUploadedBefore(LocalDateTime)` | Finds older photos for previous-photo navigation | +| `photoalbum-java-app` | `PhotoRepository` | `findPhotosUploadedAfter(LocalDateTime)` | Finds newer photos for next-photo navigation | +| `photoalbum-java-app` | `PhotoRepository` | `findPhotosByUploadMonth(String, String)` | Oracle-specific month-based filtering helper | +| `photoalbum-java-app` | `PhotoRepository` | `findPhotosWithPagination(int, int)` | Uses Oracle `ROWNUM` pagination for ordered slicing | +| `photoalbum-java-app` | `PhotoRepository` | `findPhotosWithStatistics()` | Uses Oracle analytical functions for ranking and running totals | + +## Caching Strategy + +No application-level caching was detected. There are no `@Cacheable` annotations, Spring Cache configuration classes, Redis dependencies, or Hibernate second-level cache settings in the repository. Every gallery, detail, and image request ultimately reads from Oracle, while uploaded image responses add explicit no-cache headers to avoid stale browser content. + +## Data Ownership Boundaries + +The application uses a single shared Oracle database that is owned and accessed by one Spring Boot service. There is no database-per-service split, no CQRS read model, and no cross-service access pattern beyond the web application talking directly to its own database. Transaction management is centralized in `PhotoServiceImpl`, which is annotated with `@Transactional` so upload, lookup, and delete operations run inside Spring-managed transaction boundaries. + +### Data Classification & Sensitivity + +| Entity | Sensitive Fields | Classification (PII/PHI/PCI/None) | Controls in Place | +|---|---|---|---| +| `Photo` | `originalFileName`, `photoData` | PII (potential user-generated personal content) | No explicit masking, field-level access control, or application-managed encryption detected | diff --git a/.github/modernize/assessment/reports/report-20260521061104/facts/dependency-map.md b/.github/modernize/assessment/reports/report-20260521061104/facts/dependency-map.md new file mode 100644 index 000000000..fff7e6e5d --- /dev/null +++ b/.github/modernize/assessment/reports/report-20260521061104/facts/dependency-map.md @@ -0,0 +1,69 @@ +# Dependency Map + +This project is a Spring Boot Maven application with 8 declared non-test dependencies plus 2 test-scoped dependencies. The dependency set is compact and centered around web delivery, JPA-based persistence, Oracle connectivity, and a small number of utility libraries. + +## Dependencies + +```mermaid +flowchart LR + App["photo-album application"] + Parent["spring-boot-starter-parent 2.7.18"] + + subgraph Web["Web Frameworks"] + WebStarter["spring-boot-starter-web 2.7.18"] + Thymeleaf["spring-boot-starter-thymeleaf 2.7.18"] + end + + subgraph Db["Database and ORM"] + Jpa["spring-boot-starter-data-jpa 2.7.18"] + OracleJdbc["ojdbc8 managed runtime"] + end + + subgraph Util["Utilities"] + Validation["spring-boot-starter-validation 2.7.18"] + Json["spring-boot-starter-json 2.7.18"] + CommonsIo["commons-io 2.11.0"] + Devtools["spring-boot-devtools 2.7.18 optional"] + end + + App -->|"web"| Web + App -->|"persistence"| Db + App -->|"support"| Util + Parent -.->|"manages versions"| WebStarter + Parent -.->|"manages versions"| Thymeleaf + Parent -.->|"manages versions"| Jpa + Parent -.->|"manages versions"| Validation + Parent -.->|"manages versions"| Json + Parent -.->|"manages versions"| Devtools + Parent -.->|"manages BOM for"| OracleJdbc +``` + +### Dependency Summary + +| Category | Count | Key Libraries | Notes | +|---|---:|---|---| +| Web Frameworks | 2 | Spring Boot Web, Spring Boot Thymeleaf | Server-rendered MVC UI with template rendering | +| Database / ORM | 2 | Spring Data JPA, ojdbc8 | Hibernate-backed persistence against Oracle | +| Utilities | 4 | Validation, JSON, Commons IO, DevTools | Validation, JSON serialization, file helpers, and development reload support | + +### Version & Compatibility Risks + +The dependency set is tied to Spring Boot 2.7.18 and Java 8, both of which are older baselines for Azure modernization scenarios and are already highlighted by the generated AppCAT report. The Oracle JDBC runtime dependency also keeps the application closely coupled to Oracle-specific behavior, while the JPA repository contains native Oracle SQL that may complicate migration to other managed data platforms. + +### Notable Observations + +- The Spring Boot parent POM centrally manages nearly all starter versions, so framework upgrades will have broad impact across web, JPA, validation, and JSON support. +- `commons-io` is the only explicitly version-pinned third-party utility outside the Spring Boot managed dependency set. +- The application declares Oracle JDBC only at runtime scope, which is appropriate for deployment but still creates a hard production dependency on Oracle drivers. +- No dedicated messaging, caching, security, or observability libraries are declared in the build. + +## Test Dependencies + +| Framework | Version | Notes | +|---|---|---| +| `spring-boot-starter-test` | 2.7.18 | Provides JUnit 5 and the default Spring Boot test stack | +| `h2` | Spring Boot managed | In-memory database dependency used for tests | + +Total test-scope dependencies: 2 + +The test toolchain is minimal and focused on application context startup validation. No dedicated integration-test libraries, containerized test dependencies, or contract-testing frameworks are declared. diff --git a/.github/modernize/assessment/reports/report-20260521061104/report.html b/.github/modernize/assessment/reports/report-20260521061104/report.html new file mode 100644 index 000000000..8d1f35bfc --- /dev/null +++ b/.github/modernize/assessment/reports/report-20260521061104/report.html @@ -0,0 +1,1378 @@ + + + +Assessment - photo-album + + + + + +
+

photo-album

+
+

Application Information

+
+
+
+
Application Namephoto-album
+
Java Version1.8
+
EffortL (total story points: 51)
+
+
+
Build ToolsMaven
+
FrameworksSpring Boot, Spring
+
+
+
+
+ +
+
+ + +
+
+

Issue Summary

+
+
+
+ + + + +

Cloud Readiness

+
5 issues
+
+
+ + + + + +

Java Upgrade

+
4 issues
+
+
+
+
Mandatory
+
Potential
+
Optional
+
+
+
+
+
+ +
+ + +
+
+
+ +
+ + + +
+
+ Clear all +
+
+

Cloud Readiness

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Issue CategoryCriticalityStory Point
Oracle database found
Potential8
Password found in configuration file
Potential3
Server port configuration found
Potential1
Restricted configurations found
Potential2
Detects usage of Jakarta Persistence (JPA) APIs
Potential5
+
+
+

Java Upgrade

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Issue CategoryCriticalityStory Point
Spring Boot Version Has Reached the End of OSS Support
Mandatory8
Spring Framework Version Has Reached the End of OSS Support
Mandatory8
Java Version Has Reached the End of Support
Mandatory8
Java Version is not the latest LTS
Optional8
+
+
+
+

Architecture Diagram

+

This document summarizes the Photo Album application's runtime structure and the main component relationships that support photo upload, gallery browsing, image retrieval, and deletion.

+

Application Architecture

+
+flowchart TD + subgraph Client["Client Layer"] + Browser["Web Browser"] + end + + subgraph App["Application Layer - Spring Boot 2.7"] + Views["Thymeleaf Views"] + UploadJs["Upload JavaScript"] + HomeCtrl["HomeController"] + DetailCtrl["DetailController"] + FileCtrl["PhotoFileController"] + PhotoSvc["PhotoServiceImpl"] + end + + subgraph Data["Data Layer"] + PhotoRepo["Spring Data JPA Repository"] + OracleDB[("Oracle Database PHOTOS table")] + end + + subgraph External["External Services"] + BootstrapCdn["Bootstrap CDN"] + OracleContainer["Oracle DB Container"] + end + + Browser -->|"Loads gallery and detail pages"| Views + Browser -->|"Drag and drop uploads"| UploadJs + Views -->|"GET / and GET /detail/{id}"| HomeCtrl + Views -->|"Photo detail navigation and delete"| DetailCtrl + UploadJs -->|"POST /upload"| HomeCtrl + Browser -->|"GET /photo/{id}"| FileCtrl + HomeCtrl -->|"List and create photo records"| PhotoSvc + DetailCtrl -->|"Lookup, navigate, delete"| PhotoSvc + FileCtrl -->|"Load BLOB content"| PhotoSvc + PhotoSvc -->|"CRUD and custom queries"| PhotoRepo + PhotoRepo -->|"SQL and BLOB persistence"| OracleDB + OracleContainer -->|"Hosts"| OracleDB + Browser -->|"Fetches UI assets"| BootstrapCdn +
+

Technology Stack Summary

+
+ + + + + + + + +
LayerTechnologyVersionPurpose
PresentationThymeleaf templates, Bootstrap, vanilla JavaScriptThymeleaf via Spring Boot 2.7.18, Bootstrap 5.3.0Renders gallery/detail pages and handles drag-and-drop uploads
WebSpring Boot Web, Spring MVC2.7.18Handles HTTP requests and server-side page composition
Business LogicPhotoServiceImplApplication codeValidates uploads, extracts metadata, coordinates persistence
Data AccessSpring Data JPA, HibernateSpring Boot managedMaps the Photo entity to Oracle and executes repository queries
StorageOracle Database Free/XEContainer image latest / XE-compatible JDBC URLStores photo metadata and image BLOB data
DeliveryDocker, Docker ComposeDockerfile + composeRuns the web app and Oracle database as containers
+

Data Storage & External Services

+

The application stores all photo metadata and binary image content in a single Oracle database table named PHOTOS. Beyond the database, the only notable external dependency at runtime is the Bootstrap CDN used by the server-rendered UI for styling and client-side bundle delivery.

+

Key Architectural Decisions

+
    +
  • Uses a classic layered Spring MVC design where controllers delegate all business logic and persistence orchestration to a single PhotoServiceImpl service.
  • +
  • Persists uploaded images as Oracle BLOB data instead of filesystem storage, which keeps the application stateless from a file-hosting perspective.
  • +
  • Uses server-rendered Thymeleaf pages for navigation while enhancing uploads with client-side JavaScript for drag-and-drop and immediate gallery updates.
  • +
+

Component Relationships

+
+flowchart LR + subgraph Presentation["Presentation"] + IndexView["index.html"] + DetailView["detail.html"] + UploadClient["upload.js"] + HomeControllerNode["HomeController"] + DetailControllerNode["DetailController"] + PhotoFileControllerNode["PhotoFileController"] + end + + subgraph Business["Business Logic"] + PhotoServiceNode["PhotoService"] + PhotoServiceImplNode["PhotoServiceImpl"] + UploadResultNode["UploadResult"] + end + + subgraph DataAccess["Data Access"] + PhotoRepoNode["PhotoRepository"] + PhotoEntityNode["Photo Entity"] + end + + subgraph Infra["Infrastructure"] + SpringBootNode["Spring Boot Runtime"] + TxNode["Transactional Boundary"] + OracleNode["Oracle Database"] + end + + IndexView -->|"submits uploads to"| UploadClient + IndexView -->|"binds model data from"| HomeControllerNode + DetailView -->|"binds model data from"| DetailControllerNode + UploadClient -->|"calls"| HomeControllerNode + HomeControllerNode -->|"delegates"| PhotoServiceNode + DetailControllerNode -->|"delegates"| PhotoServiceNode + PhotoFileControllerNode -->|"delegates"| PhotoServiceNode + PhotoServiceNode -->|"implemented by"| PhotoServiceImplNode + PhotoServiceImplNode -->|"returns"| UploadResultNode + PhotoServiceImplNode -->|"queries and saves"| PhotoRepoNode + PhotoRepoNode -->|"maps"| PhotoEntityNode + PhotoRepoNode -->|"executes against"| OracleNode + SpringBootNode -.->|"creates and wires"| Presentation + TxNode -.->|"wraps service methods"| PhotoServiceImplNode +
+

Component Inventory

+
+ + + + + + + + + + + +
ComponentLayerTypeResponsibility
index.htmlPresentationThymeleaf templateDisplays the gallery, upload area, and flash/error messages
detail.htmlPresentationThymeleaf templateShows a single photo, metadata, navigation, and delete action
upload.jsPresentationBrowser scriptValidates dropped files, posts multipart uploads, and prepends new cards to the gallery
HomeControllerPresentationMVC controllerHandles gallery rendering and upload responses
DetailControllerPresentationMVC controllerHandles detail page rendering and delete operations
PhotoFileControllerPresentationMVC controllerStreams image bytes with cache-control headers
PhotoServiceImplBusiness LogicServiceApplies upload rules, extracts image dimensions, and coordinates CRUD operations
PhotoRepositoryData AccessSpring Data repositoryExecutes Oracle-specific native SQL for ordered listing and navigation
PhotoData AccessJPA entityRepresents persisted photo metadata and BLOB content
+
+
+

API & Service Communication Contracts

+

The application exposes a small HTTP surface made up of one gallery page, one upload endpoint, one image streaming endpoint, and detail/delete routes for individual photos. Communication is entirely synchronous: browser requests are handled by Spring MVC controllers, which call a local service and persist to Oracle through JPA.

+

Service Catalog

+
+ + + + +
ServicePortCategoryPurpose
photoalbum-java-app8080API LayerServes gallery/detail pages, accepts uploads, streams photo content, and deletes photos
oracle-db1521InfrastructureStores photo metadata and image BLOB data for the web application
+

API Endpoints Inventory

+
+ + + + + + + +
ServiceMethodPathRequest TypeResponse Type
photoalbum-java-appGET/NoneThymeleaf index.html view with photos model data
photoalbum-java-appPOST/uploadMultipart form field files containing one or more MultipartFile objectsJSON Map with success, uploadedPhotos, and failedUploads
photoalbum-java-appGET/detail/{id}Path parameter id:StringThymeleaf detail.html view or redirect to / when not found
photoalbum-java-appPOST/detail/{id}/deletePath parameter id:StringRedirect to / with flash success/error message
photoalbum-java-appGET/photo/{id}Path parameter id:StringResponseEntity image stream with 200, 404, or 500 status
+

Management & Observability Endpoints

+
+ + + +
ServiceEndpointCustom Metrics
photoalbum-java-appNone detectedNo Actuator, health, metrics, or Swagger endpoints detected
+

DTOs & Contracts

+

The application does not define dedicated request DTO classes for its HTTP surface. Upload requests are accepted as Spring MultipartFile collections, while server-rendered views bind directly to the Photo domain entity for display purposes. The only explicit contract helper class is UploadResult, which is used internally by the service layer to communicate per-file outcomes before the controller converts those results into an ad hoc JSON map.

+

No gateway-level aggregation DTOs, OpenAPI specifications, protobuf schemas, or GraphQL schemas were found. JSON serialization relies on Spring Boot's default Jackson configuration through spring-boot-starter-json.

+

Communication Patterns

+

All communication is synchronous. Browser clients call Spring MVC controllers over HTTP, controllers invoke PhotoServiceImpl through direct in-process method calls, and the service uses PhotoRepository for synchronous Oracle database access. No asynchronous messaging, background workers, service discovery, API gateway, client-side load balancing, retry policy, circuit breaker, or timeout library was detected.

+

API availability depends on the Oracle database container being healthy before the web container starts; Docker Compose enforces this startup dependency with depends_on and a database health check. Security posture is minimal: no TLS termination, authentication, authorization rules, or Spring Security configuration were found, so all endpoints are publicly accessible within the deployment environment.

+

Service Technology Matrix

+
+ + + + +
ServiceWebData AccessDiscoveryGatewayActuatorCacheMetrics
photoalbum-java-appSpring MVC + ThymeleafSpring Data JPA / HibernateNoneNoneNoneNoneNone
oracle-dbN/AOracle database engineNoneNoneHealth check onlyN/ANone
+

Service Communication Sequence

+
+sequenceDiagram + participant Browser + participant Home as "HomeController" + participant Service as "PhotoServiceImpl" + participant Repo as "PhotoRepository" + participant DB as "Oracle Database" + + Browser->>Home: POST /upload (multipart files) + Home->>Service: uploadPhoto(file) + alt File passes validation + Service->>Service: Validate MIME type and size + Service->>Service: Read bytes and image dimensions + Service->>Repo: save(Photo) + Repo->>DB: INSERT photo metadata and BLOB + DB-->>Repo: Stored row + Repo-->>Service: Persisted Photo + Home->>Service: getPhotoById(photoId) + Service->>Repo: findById(photoId) + Repo->>DB: SELECT photo by id + DB-->>Repo: Photo row + Repo-->>Service: Photo + Service-->>Home: Upload success result + Home-->>Browser: 200 JSON with uploadedPhotos + else Validation or persistence failure + Service-->>Home: UploadResult failure + Home-->>Browser: 200 JSON with failedUploads or 400 for missing files + end +
+
+
+

Configuration & Externalized Settings Inventory

+

The application uses a small set of local configuration sources: Spring property files, Docker Compose service definitions, Docker image settings, and Oracle initialization scripts. Configuration is straightforward, but secrets are stored inline in repository-managed files rather than external secret stores.

+

Configuration Sources

+
+ + + + + + + + + +
SourceTypePath/LocationNotes
Spring application propertiesRuntime configsrc/main/resources/application.propertiesDefault server, datasource, JPA, upload, and logging settings
Spring profile propertiesRuntime configsrc/main/resources/application-docker.propertiesDocker-specific Oracle connection and logging overrides
Docker ComposeContainer orchestration configdocker-compose.ymlDefines web and Oracle services, environment variables, networking, and startup order
DockerfileImage/runtime configDockerfileDefines build image, runtime image, JVM options, and exposed port
Maven build fileBuild configpom.xmlDeclares framework versions and dependencies
Oracle init SQLDatabase bootstrap configoracle-init/*.sqlCreates database user, verifies privileges, and supports health checks
Test annotation configTest runtime signalsrc/test/java/com/photoalbum/PhotoAlbumApplicationTests.javaActivates the test Spring profile during context-load testing
+

Build Profiles

+
+ + + +
ProfileActivationPurposeKey Dependencies/Plugins
Default Maven buildAutomaticStandard packaging of the Spring Boot jarspring-boot-maven-plugin; no explicit Maven detected
+

Runtime Profiles

+
+ + + + + +
ProfileActivation MethodConfig FilesKey Overrides
defaultAutomatic when no profile is setapplication.propertiesOracle FREEPDB1 JDBC URL, debug logging for app and Spring Web, upload limits
dockerSPRING_PROFILES_ACTIVE=docker in Docker Composeapplication.properties, application-docker.propertiesOracle XE JDBC URL, lower web logging verbosity, container-oriented datasource host
test@ActiveProfiles("test") on test classBase properties plus Spring Boot test defaultsUses test-scoped H2 dependency; no dedicated application-test.properties file detected
+

Properties Inventory

+

photoalbum-java-app

+
+ + + + + + + + + + + + + + + + + + + + + + +
Property KeyDefaultProfilesSource
server.port8080default, dockerSpring properties files
server.servlet.encoding.charsetUTF-8default, dockerSpring properties files
server.servlet.encoding.enabledtruedefault, dockerSpring properties files
server.servlet.encoding.forcetruedefault, dockerSpring properties files
spring.datasource.urlOracle JDBC URLdefault, dockerSpring properties files
spring.datasource.usernamephotoalbumdefault, dockerSpring properties files
spring.datasource.password[MASKED]default, dockerSpring properties files
spring.datasource.driver-class-nameoracle.jdbc.OracleDriverdefault, dockerSpring properties files
spring.jpa.database-platformorg.hibernate.dialect.OracleDialectdefault, dockerSpring properties files
spring.jpa.hibernate.ddl-autocreatedefault, dockerSpring properties files
spring.jpa.show-sqltruedefault, dockerSpring properties files
spring.jpa.properties.hibernate.format_sqltruedefault, dockerSpring properties files
spring.servlet.multipart.max-file-size10MBdefault, dockerSpring properties files
spring.servlet.multipart.max-request-size50MBdefault, dockerSpring properties files
app.file-upload.max-file-size-bytes10485760default, dockerSpring properties files
app.file-upload.allowed-mime-typesimage/jpeg,image/png,image/gif,image/webpdefault, dockerSpring properties files
app.file-upload.max-files-per-upload10default, dockerSpring properties files
logging.level.com.photoalbumDEBUG or INFOdefault, dockerSpring properties files
logging.level.org.springframework.webDEBUG or WARNdefault, dockerSpring properties files
logging.level.org.hibernate.SQLDEBUGdocker onlyapplication-docker.properties
+

docker-compose service environment overrides

+
+ + + + + + + + + +
Property KeyDefaultProfilesSource
SPRING_PROFILES_ACTIVEdockerdockerdocker-compose.yml
SPRING_DATASOURCE_URLOracle JDBC URLdockerdocker-compose.yml
SPRING_DATASOURCE_USERNAMEphotoalbumdockerdocker-compose.yml
SPRING_DATASOURCE_PASSWORD[MASKED]dockerdocker-compose.yml
ORACLE_PASSWORD[MASKED]dockerdocker-compose.yml
APP_USERphotoalbumdockerdocker-compose.yml
APP_USER_PASSWORD[MASKED]dockerdocker-compose.yml
+

Startup Parameters & Resource Requirements

+
+ + + + +
ServiceJVM/Runtime OptionsMemoryInstance Count
photoalbum-java-appJAVA_OPTS="-Xmx512m -Xms256m"; exposes port 8080256 MB initial heap, 512 MB max heap1 container in Compose
oracle-dbNo JVM options; Oracle container health check scriptNo explicit memory limit configured1 container in Compose
+

Startup Dependency Chain

+
    +
  1. oracle-db starts first and initializes the database user via scripts mounted from oracle-init/.
  2. +
  3. Docker Compose waits for the Oracle container health check (healthcheck.sh) to report healthy.
  4. +
  5. photoalbum-java-app starts only after oracle-db is healthy because of depends_on.condition: service_healthy.
  6. +
  7. The Spring Boot application then connects to Oracle using the active datasource settings and creates the schema on startup.
  8. +
+

Secrets & Sensitive Configuration

+
+ + + + + + +
Secret ReferenceTypeStorage (masked)
spring.datasource.passwordDatabase passwordInline Spring properties value masked in report
SPRING_DATASOURCE_PASSWORDDatabase passwordDocker Compose environment variable masked in report
ORACLE_PASSWORDOracle system/admin passwordDocker Compose environment variable masked in report
APP_USER_PASSWORDOracle application user passwordDocker Compose environment variable masked in report
+

Secrets Provisioning Workflow

+

Secrets are provisioned locally and statically. Source values are checked into repository-managed Spring properties and Docker Compose environment variables, then injected directly into the application container and Oracle container at startup. No managed identity, external vault, RBAC-governed secret store, or deployment-time secret binding workflow was detected.

+

Feature Flags

+
+ + + +
Flag NameDefaultControlled By
None detectedN/ANo @ConditionalOnProperty, feature flag framework, or toggle file detected
+

Framework & Runtime Versions

+
+ + + + + + + + + + +
ComponentVersionSource
Spring Boot2.7.18pom.xml parent POM
Java target1.8pom.xml properties
Maven build image3.9.6-eclipse-temurin-8Dockerfile
Java runtime imageeclipse-temurin:8-jreDockerfile
Bootstrap5.3.0CDN reference in Thymeleaf templates
Oracle JDBCojdbc8 (managed)pom.xml dependency
Oracle database containergvenzl/oracle-free:latestdocker-compose.yml
Spring Data JPA / HibernateSpring Boot managedpom.xml dependency set
+
+
+

Core Business Workflows

+

The Photo Album application supports a simple user journey: upload image files, browse the gallery, inspect photo details, and remove photos when no longer needed. Its business logic is concentrated around validating upload eligibility, preserving metadata, and presenting stored photos in a user-friendly order.

+

Domain Entities

+
+ + + + +
EntityService / Bounded ContextDescriptionKey Relationships
PhotoPhoto ManagementCore business record representing a user-uploaded image plus its display metadataServes as the source of truth for gallery cards, detail views, navigation, and deletion
UploadResultPhoto ManagementTransient workflow object describing whether an individual file upload succeeded or failedProduced during upload processing and converted into the JSON response payload
+

Service-to-Domain Mapping

+
+ + + + +
ServiceDomain ContextOwned EntitiesExternal Dependencies
photoalbum-java-appPhoto ManagementPhoto, UploadResultOracle database for persistence; browser clients for UI-driven workflows
oracle-dbPersistence InfrastructurePhysical storage for Photo recordsNone beyond its role as the backing data store
+

Primary Workflows

+

Workflow 1: Upload Photos

+

A user drags photos into the gallery page or selects them through the file picker. The browser script performs a first pass of MIME type and file size validation, then posts the accepted files to /upload. The service layer validates each file again, rejects unsupported or empty uploads, extracts image dimensions when possible, assigns a UUID-based stored filename for compatibility, saves the photo bytes and metadata, and returns a per-file success or failure outcome.

+

Business rules involved:

+
    +
  • Only JPEG, PNG, GIF, and WebP files are accepted.
  • +
  • Each file must be non-empty and no larger than 10 MB.
  • +
  • Upload success is reported per file so mixed-result batches can still partially succeed.
  • +
  • Image dimension extraction is best-effort; failure to read dimensions does not block persistence.
  • +
+ +

When a user opens /, the application retrieves all photos ordered by most recent upload time and renders them as gallery cards. Selecting a card opens /detail/{id}, where the application loads the chosen photo, computes previous/next navigation targets based on upload timestamps, and displays metadata such as file size, MIME type, and image dimensions.

+

Business rules involved:

+
    +
  • Gallery ordering is newest-first.
  • +
  • Missing photo identifiers gracefully redirect the user back to the gallery.
  • +
  • Previous and next navigation is derived from upload time, not filename or insertion order.
  • +
+

Workflow 3: Delete a Photo

+

From the detail page, a user can submit the delete action for a specific photo. The service checks whether the record exists, removes it from the database when present, and redirects the user back to the gallery with a success or error flash message.

+

Business rules involved:

+
    +
  • Deletion only succeeds when the target photo exists.
  • +
  • The UI always returns the user to the gallery after the operation.
  • +
  • User confirmation is required in the browser before the delete form is submitted.
  • +
+

Cross-Service Data Flows

+

There is no multi-service business composition layer in this application. Business data flows from the browser to the Spring Boot application and then to Oracle, with the same service acting as the source of truth for validation, persistence, and presentation. When failures occur, the application degrades in business-visible ways: upload batches return per-file error messages, missing detail records redirect to the gallery, and delete attempts surface a user-facing error message instead of throwing a visible exception.

+

Business Workflow Sequence

+
+sequenceDiagram + participant User + participant Browser as "Gallery UI" + participant Home as "HomeController" + participant Service as "PhotoServiceImpl" + participant Repo as "PhotoRepository" + participant DB as "Oracle Database" + + User->>Browser: Drop image files into upload zone + Browser->>Browser: Validate file type and size client-side + Browser->>Home: Submit accepted files to /upload + Home->>Service: Process each file + Service->>Service: Enforce upload rules and extract metadata + alt File is valid + Service->>Repo: Save Photo record + Repo->>DB: Persist metadata and image BLOB + DB-->>Repo: Stored photo + Repo-->>Service: Persisted entity + Service-->>Home: Success with photo id + Home-->>Browser: JSON response with uploaded photo summary + Browser-->>User: Show success and prepend new gallery card + else File is invalid or save fails + Service-->>Home: Failure result with error message + Home-->>Browser: JSON response with failedUploads entry + Browser-->>User: Show upload error message + end +
+

Business Rules & Decision Logic

+
    +
  • Validation rules: The application accepts only JPEG, PNG, GIF, and WebP uploads, enforces a 10 MB per-file limit, and rejects empty files.
  • +
  • Decision logic: Upload processing follows a per-file success/failure model so one bad file does not block the entire batch.
  • +
  • Computed values: The service derives image width and height from image bytes when available, and the UI computes human-readable file size displays and formatted upload timestamps.
  • +
  • State transitions: A Photo record moves from not persisted to persisted on upload and from persisted to removed on delete; there are no intermediate approval states.
  • +
  • Transactions: PhotoServiceImpl is annotated with @Transactional, so persistence operations run inside Spring-managed transaction boundaries.
  • +
  • Error handling and authorization: The application logs operational failures and converts them into redirects or structured upload errors. No business-level authorization rules or role checks were detected.
  • +
+
+
+

Dependency Map

+

This project is a Spring Boot Maven application with 8 declared non-test dependencies plus 2 test-scoped dependencies. The dependency set is compact and centered around web delivery, JPA-based persistence, Oracle connectivity, and a small number of utility libraries.

+

Dependencies

+
+flowchart LR + App["photo-album application"] + Parent["spring-boot-starter-parent 2.7.18"] + + subgraph Web["Web Frameworks"] + WebStarter["spring-boot-starter-web 2.7.18"] + Thymeleaf["spring-boot-starter-thymeleaf 2.7.18"] + end + + subgraph Db["Database and ORM"] + Jpa["spring-boot-starter-data-jpa 2.7.18"] + OracleJdbc["ojdbc8 managed runtime"] + end + + subgraph Util["Utilities"] + Validation["spring-boot-starter-validation 2.7.18"] + Json["spring-boot-starter-json 2.7.18"] + CommonsIo["commons-io 2.11.0"] + Devtools["spring-boot-devtools 2.7.18 optional"] + end + + App -->|"web"| Web + App -->|"persistence"| Db + App -->|"support"| Util + Parent -.->|"manages versions"| WebStarter + Parent -.->|"manages versions"| Thymeleaf + Parent -.->|"manages versions"| Jpa + Parent -.->|"manages versions"| Validation + Parent -.->|"manages versions"| Json + Parent -.->|"manages versions"| Devtools + Parent -.->|"manages BOM for"| OracleJdbc +
+

Dependency Summary

+
+ + + + + +
CategoryCountKey LibrariesNotes
Web Frameworks2Spring Boot Web, Spring Boot ThymeleafServer-rendered MVC UI with template rendering
Database / ORM2Spring Data JPA, ojdbc8Hibernate-backed persistence against Oracle
Utilities4Validation, JSON, Commons IO, DevToolsValidation, JSON serialization, file helpers, and development reload support
+

Version & Compatibility Risks

+

The dependency set is tied to Spring Boot 2.7.18 and Java 8, both of which are older baselines for Azure modernization scenarios and are already highlighted by the generated AppCAT report. The Oracle JDBC runtime dependency also keeps the application closely coupled to Oracle-specific behavior, while the JPA repository contains native Oracle SQL that may complicate migration to other managed data platforms.

+

Notable Observations

+
    +
  • The Spring Boot parent POM centrally manages nearly all starter versions, so framework upgrades will have broad impact across web, JPA, validation, and JSON support.
  • +
  • commons-io is the only explicitly version-pinned third-party utility outside the Spring Boot managed dependency set.
  • +
  • The application declares Oracle JDBC only at runtime scope, which is appropriate for deployment but still creates a hard production dependency on Oracle drivers.
  • +
  • No dedicated messaging, caching, security, or observability libraries are declared in the build.
  • +
+

Test Dependencies

+
+ + + + +
FrameworkVersionNotes
spring-boot-starter-test2.7.18Provides JUnit 5 and the default Spring Boot test stack
h2Spring Boot managedIn-memory database dependency used for tests
+

Total test-scope dependencies: 2

+

The test toolchain is minimal and focused on application context startup validation. No dedicated integration-test libraries, containerized test dependencies, or contract-testing frameworks are declared.

+
+
+

Data Architecture & Persistence Layer

+

The data layer is intentionally small: a single JPA entity is stored in Oracle using Spring Data JPA and Hibernate. All persistence concerns are concentrated in one repository interface, with no caching layer or separate reporting store.

+

Database Configuration

+
+ + + + + +
Service/ModuleDB TypeProfileDriverConnectionMigration Tool
photoalbum-java-appOracle DatabasedefaultOracle JDBC (ojdbc8)JDBC URL points to oracle-db:1521/FREEPDB1None; Hibernate creates schema at startup
photoalbum-java-appOracle Database XEdockerOracle JDBC (ojdbc8)JDBC URL points to oracle-db:1521:XENone; Hibernate creates schema at startup
photoalbum-java-app testsH2 in-memory databasetestH2Spring Boot test auto-configuration with @ActiveProfiles("test")None detected
+

Data Ownership per Service

+
+ + + + +
ServiceTables OwnedORM FrameworkCachingNotes
photoalbum-java-appPHOTOSJPA / Hibernate via Spring Data JPANone detectedSingle-service ownership; stores both metadata and binary photo content
oracle-dbPHOTOS physical storageOracle engineN/ABacking store only; no independent domain ownership
+

Entity Model

+
+erDiagram + Photo { + string id PK + string originalFileName + bytes photoData + string storedFileName + string filePath + long fileSize + string mimeType + datetime uploadedAt + int width + int height + } +
+

The source entity model is defined in src/main/java/com/photoalbum/model/Photo.java. No JPA relationships to other entities were found; the model is a self-contained aggregate for photo content and metadata.

+

Key Repository Methods

+
+ + + + + + + + +
ServiceRepositoryNotable MethodsPurpose
photoalbum-java-appPhotoRepository (src/main/java/com/photoalbum/repository/PhotoRepository.java)findAllOrderByUploadedAtDesc()Returns gallery items ordered newest-first
photoalbum-java-appPhotoRepositoryfindPhotosUploadedBefore(LocalDateTime)Finds older photos for previous-photo navigation
photoalbum-java-appPhotoRepositoryfindPhotosUploadedAfter(LocalDateTime)Finds newer photos for next-photo navigation
photoalbum-java-appPhotoRepositoryfindPhotosByUploadMonth(String, String)Oracle-specific month-based filtering helper
photoalbum-java-appPhotoRepositoryfindPhotosWithPagination(int, int)Uses Oracle ROWNUM pagination for ordered slicing
photoalbum-java-appPhotoRepositoryfindPhotosWithStatistics()Uses Oracle analytical functions for ranking and running totals
+

Caching Strategy

+

No application-level caching was detected. There are no @Cacheable annotations, Spring Cache configuration classes, Redis dependencies, or Hibernate second-level cache settings in the repository. Every gallery, detail, and image request ultimately reads from Oracle, while uploaded image responses add explicit no-cache headers to avoid stale browser content.

+

Data Ownership Boundaries

+

The application uses a single shared Oracle database that is owned and accessed by one Spring Boot service. There is no database-per-service split, no CQRS read model, and no cross-service access pattern beyond the web application talking directly to its own database. Transaction management is centralized in PhotoServiceImpl, which is annotated with @Transactional so upload, lookup, and delete operations run inside Spring-managed transaction boundaries.

+

Data Classification & Sensitivity

+
+ + + +
EntitySensitive FieldsClassification (PII/PHI/PCI/None)Controls in Place
PhotooriginalFileName, photoDataPII (potential user-generated personal content)No explicit masking, field-level access control, or application-managed encryption detected
+
+ + + +
\ No newline at end of file diff --git a/.github/modernize/assessment/reports/report-20260521061104/report.json b/.github/modernize/assessment/reports/report-20260521061104/report.json new file mode 100644 index 000000000..ad74761d9 --- /dev/null +++ b/.github/modernize/assessment/reports/report-20260521061104/report.json @@ -0,0 +1,1021 @@ +{ + "version": "1.0.0", + "producer": "Java AppCAT CLI", + "metadata": { + "analysisStartTime": "2026-05-21T06:11:04.086914437Z", + "analysisEndTime": "2026-05-21T06:11:50.820300156Z", + "status": "Complete", + "privacyMode": "Protected", + "privacyModeHelpUrl": "https://aka.ms/appcat-privacy-mode", + "targetIds": [ + "azure-aks", + "azure-appservice", + "azure-container-apps" + ], + "targetDisplayNames": [ + "Azure Kubernetes Service", + "Azure App Service", + "Azure Container Apps" + ], + "capabilities": [], + "os": [] + }, + "summary": { + "totalProjects": 1, + "totalIssues": 9, + "totalIncidents": 27, + "totalEffort": 172, + "charts": { + "severity": { + "mandatory": 11, + "optional": 2, + "potential": 14, + "information": 0 + }, + "category": { + "database-migration": 6, + "framework-upgrade": 10, + "jakarta-migration": 1, + "java-version-upgrade": 3, + "local-credential": 3, + "spring-migration": 4 + } + } + }, + "projects": [ + { + "path": ".", + "issues": 9, + "storyPoints": 172, + "properties": { + "appName": "photo-album", + "jdkVersion": "1.8", + "frameworks": [ + "Spring Boot", + "Spring" + ], + "languages": [ + "Java", + "Python" + ], + "tools": [ + "Maven" + ] + }, + "incidents": [ + { + "ruleId": "azure-java-version-02000", + "incidentId": "18706532-081f-49f1-badc-4aade976a6c5", + "location": "pom.xml", + "locationKind": "File", + "line": 25, + "column": 0, + "targets": { + "azure-aks": { + "effort": 8, + "severity": "optional" + }, + "azure-appservice": { + "effort": 8, + "severity": "optional" + }, + "azure-container-apps": { + "effort": 8, + "severity": "optional" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "azure-java-version-02000", + "incidentId": "9180d72e-330d-4ab5-abb3-c1112cd28d08", + "location": "pom.xml", + "locationKind": "File", + "line": 26, + "column": 0, + "targets": { + "azure-aks": { + "effort": 8, + "severity": "optional" + }, + "azure-appservice": { + "effort": 8, + "severity": "optional" + }, + "azure-container-apps": { + "effort": 8, + "severity": "optional" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "spring-boot-to-azure-port-01000", + "incidentId": "99358692-970d-423f-8290-674bea48d9cf", + "location": "src/main/resources/application-docker.properties", + "locationKind": "File", + "line": 24, + "column": 0, + "targets": { + "azure-aks": { + "effort": 1, + "severity": "potential" + }, + "azure-appservice": { + "effort": 1, + "severity": "potential" + }, + "azure-container-apps": { + "effort": 1, + "severity": "potential" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "spring-boot-to-azure-port-01000", + "incidentId": "9bcd3042-1025-4c7e-beda-583651910211", + "location": "src/main/resources/application.properties", + "locationKind": "File", + "line": 2, + "column": 0, + "targets": { + "azure-aks": { + "effort": 1, + "severity": "potential" + }, + "azure-appservice": { + "effort": 1, + "severity": "potential" + }, + "azure-container-apps": { + "effort": 1, + "severity": "potential" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "spring-boot-to-azure-restricted-config-01000", + "incidentId": "7f6411a1-1abc-4cc5-9c06-50073793415d", + "location": "src/main/resources/application-docker.properties", + "locationKind": "File", + "line": 24, + "column": 0, + "targets": { + "azure-container-apps": { + "effort": 2, + "severity": "potential" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "spring-boot-to-azure-restricted-config-01000", + "incidentId": "8a9f0cd9-5e43-4576-ac10-6e3b9924429b", + "location": "src/main/resources/application.properties", + "locationKind": "File", + "line": 2, + "column": 0, + "targets": { + "azure-container-apps": { + "effort": 2, + "severity": "potential" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "spring-boot-to-azure-spring-boot-version-01000", + "incidentId": "da4bc4c0-e33f-40bf-855f-84869f9a8fbd", + "location": "pom.xml", + "locationKind": "File", + "line": 40, + "column": 0, + "targets": { + "azure-aks": { + "effort": 8, + "severity": "mandatory" + }, + "azure-appservice": { + "effort": 8, + "severity": "mandatory" + }, + "azure-container-apps": { + "effort": 8, + "severity": "mandatory" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "spring-boot-to-azure-spring-boot-version-01000", + "incidentId": "eff335ba-52da-40dc-a4c5-a51cbbb20ce3", + "location": "pom.xml", + "locationKind": "File", + "line": 78, + "column": 0, + "targets": { + "azure-aks": { + "effort": 8, + "severity": "mandatory" + }, + "azure-appservice": { + "effort": 8, + "severity": "mandatory" + }, + "azure-container-apps": { + "effort": 8, + "severity": "mandatory" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "spring-boot-to-azure-spring-boot-version-01000", + "incidentId": "1a234247-d219-456d-a286-c844db01e34e", + "location": "pom.xml", + "locationKind": "File", + "line": 34, + "column": 0, + "targets": { + "azure-aks": { + "effort": 8, + "severity": "mandatory" + }, + "azure-appservice": { + "effort": 8, + "severity": "mandatory" + }, + "azure-container-apps": { + "effort": 8, + "severity": "mandatory" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "spring-boot-to-azure-spring-boot-version-01000", + "incidentId": "52aab8c8-018f-4c39-bcaf-2a941928d2d6", + "location": "pom.xml", + "locationKind": "File", + "line": 72, + "column": 0, + "targets": { + "azure-aks": { + "effort": 8, + "severity": "mandatory" + }, + "azure-appservice": { + "effort": 8, + "severity": "mandatory" + }, + "azure-container-apps": { + "effort": 8, + "severity": "mandatory" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "spring-boot-to-azure-spring-boot-version-01000", + "incidentId": "f093a77b-2fc9-48a5-ba59-98ab2620d7b8", + "location": "pom.xml", + "locationKind": "File", + "line": 59, + "column": 0, + "targets": { + "azure-aks": { + "effort": 8, + "severity": "mandatory" + }, + "azure-appservice": { + "effort": 8, + "severity": "mandatory" + }, + "azure-container-apps": { + "effort": 8, + "severity": "mandatory" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "spring-boot-to-azure-spring-boot-version-01000", + "incidentId": "499b61ac-cd15-432b-9c96-31baf0daa518", + "location": "pom.xml", + "locationKind": "File", + "line": 92, + "column": 0, + "targets": { + "azure-aks": { + "effort": 8, + "severity": "mandatory" + }, + "azure-appservice": { + "effort": 8, + "severity": "mandatory" + }, + "azure-container-apps": { + "effort": 8, + "severity": "mandatory" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "spring-boot-to-azure-spring-boot-version-01000", + "incidentId": "ff5621f5-c530-4405-aafc-cbc5d5197986", + "location": "pom.xml", + "locationKind": "File", + "line": 46, + "column": 0, + "targets": { + "azure-aks": { + "effort": 8, + "severity": "mandatory" + }, + "azure-appservice": { + "effort": 8, + "severity": "mandatory" + }, + "azure-container-apps": { + "effort": 8, + "severity": "mandatory" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "azure-database-microsoft-oracle-07000", + "incidentId": "661623e4-8d1c-4814-ae65-d6681cae26c8", + "location": "pom.xml", + "locationKind": "File", + "line": 52, + "column": 0, + "targets": { + "azure-aks": { + "effort": 8, + "severity": "potential" + }, + "azure-appservice": { + "effort": 8, + "severity": "potential" + }, + "azure-container-apps": { + "effort": 8, + "severity": "potential" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "azure-database-microsoft-oracle-07000", + "incidentId": "a92f04bf-7720-43ea-9d34-620153464eab", + "location": "docker-compose.yml", + "locationKind": "File", + "line": 32, + "column": 0, + "targets": { + "azure-aks": { + "effort": 8, + "severity": "potential" + }, + "azure-appservice": { + "effort": 8, + "severity": "potential" + }, + "azure-container-apps": { + "effort": 8, + "severity": "potential" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "azure-database-microsoft-oracle-07000", + "incidentId": "2b98576a-091f-420d-9633-302b5d4b41a8", + "location": "src/main/resources/application-docker.properties", + "locationKind": "File", + "line": 2, + "column": 0, + "targets": { + "azure-aks": { + "effort": 8, + "severity": "potential" + }, + "azure-appservice": { + "effort": 8, + "severity": "potential" + }, + "azure-container-apps": { + "effort": 8, + "severity": "potential" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "azure-database-microsoft-oracle-07000", + "incidentId": "29ec7c02-0656-4103-898f-8b6e51448e66", + "location": "src/main/resources/application.properties", + "locationKind": "File", + "line": 10, + "column": 0, + "targets": { + "azure-aks": { + "effort": 8, + "severity": "potential" + }, + "azure-appservice": { + "effort": 8, + "severity": "potential" + }, + "azure-container-apps": { + "effort": 8, + "severity": "potential" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "azure-database-microsoft-oracle-07000", + "incidentId": "a294ecb5-cfea-41a4-b6f6-1547519d8c29", + "location": "src/main/resources/application-docker.properties", + "locationKind": "File", + "line": 5, + "column": 0, + "targets": { + "azure-aks": { + "effort": 8, + "severity": "potential" + }, + "azure-appservice": { + "effort": 8, + "severity": "potential" + }, + "azure-container-apps": { + "effort": 8, + "severity": "potential" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "azure-database-microsoft-oracle-07000", + "incidentId": "fe47f4ea-2db4-48de-9889-ad6b594e9dcf", + "location": "src/main/resources/application.properties", + "locationKind": "File", + "line": 13, + "column": 0, + "targets": { + "azure-aks": { + "effort": 8, + "severity": "potential" + }, + "azure-appservice": { + "effort": 8, + "severity": "potential" + }, + "azure-container-apps": { + "effort": 8, + "severity": "potential" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "spring-framework-version-01000", + "incidentId": "59e3556f-56b8-40d9-bc2a-f275c89ef829", + "location": "pom.xml", + "locationKind": "File", + "line": 78, + "column": 0, + "targets": { + "azure-aks": { + "effort": 8, + "severity": "mandatory" + }, + "azure-appservice": { + "effort": 8, + "severity": "mandatory" + }, + "azure-container-apps": { + "effort": 8, + "severity": "mandatory" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "spring-framework-version-01000", + "incidentId": "bbe744b6-8dd9-40f3-9843-334fd5c24ad7", + "location": "pom.xml", + "locationKind": "File", + "line": 34, + "column": 0, + "targets": { + "azure-aks": { + "effort": 8, + "severity": "mandatory" + }, + "azure-appservice": { + "effort": 8, + "severity": "mandatory" + }, + "azure-container-apps": { + "effort": 8, + "severity": "mandatory" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "spring-framework-version-01000", + "incidentId": "6dc8f3bd-f4f9-40e6-91fc-e13c1885a3d3", + "location": "pom.xml", + "locationKind": "File", + "line": 46, + "column": 0, + "targets": { + "azure-aks": { + "effort": 8, + "severity": "mandatory" + }, + "azure-appservice": { + "effort": 8, + "severity": "mandatory" + }, + "azure-container-apps": { + "effort": 8, + "severity": "mandatory" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "azure-password-01000", + "incidentId": "443f124e-5067-476d-a896-32b957c849f7", + "location": "src/test/resources/application-test.properties", + "locationKind": "File", + "line": 5, + "column": 0, + "targets": { + "azure-aks": { + "effort": 3, + "severity": "potential" + }, + "azure-appservice": { + "effort": 3, + "severity": "potential" + }, + "azure-container-apps": { + "effort": 3, + "severity": "potential" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "azure-password-01000", + "incidentId": "abfd09b4-a2f1-4f0c-afce-ab1666835533", + "location": "src/main/resources/application-docker.properties", + "locationKind": "File", + "line": 4, + "column": 0, + "targets": { + "azure-aks": { + "effort": 3, + "severity": "potential" + }, + "azure-appservice": { + "effort": 3, + "severity": "potential" + }, + "azure-container-apps": { + "effort": 3, + "severity": "potential" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "azure-password-01000", + "incidentId": "a882b99a-5fb8-4f66-81ab-d12f5dcf481f", + "location": "src/main/resources/application.properties", + "locationKind": "File", + "line": 12, + "column": 0, + "targets": { + "azure-aks": { + "effort": 3, + "severity": "potential" + }, + "azure-appservice": { + "effort": 3, + "severity": "potential" + }, + "azure-container-apps": { + "effort": 3, + "severity": "potential" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "azure-java-version-01000", + "incidentId": "46babd76-99ea-4bb6-a566-797476c9d6a6", + "location": "pom.xml", + "locationKind": "File", + "line": 24, + "column": 0, + "targets": { + "azure-aks": { + "effort": 8, + "severity": "mandatory" + }, + "azure-appservice": { + "effort": 8, + "severity": "mandatory" + }, + "azure-container-apps": { + "effort": 8, + "severity": "mandatory" + } + }, + "labels": [ + "type=violation", + "ruleset=azure/springboot" + ] + }, + { + "ruleId": "jakarta-database-00002", + "incidentId": "aa7ff01e-3a02-4b38-9813-ecc05ebf42bc", + "location": "pom.xml", + "locationKind": "File", + "line": 46, + "column": 0, + "targets": { + "azure-aks": { + "effort": 5, + "severity": "potential" + }, + "azure-appservice": { + "effort": 5, + "severity": "potential" + }, + "azure-container-apps": { + "effort": 5, + "severity": "potential" + } + }, + "labels": [ + "type=violation", + "ruleset=cloud-readiness" + ] + } + ] + } + ], + "rules": { + "azure-database-microsoft-oracle-07000": { + "id": "azure-database-microsoft-oracle-07000", + "description": "Oracle database found. To migrate a Java application that uses an Oracle database to Azure, you can follow these recommendations:\n\n * **Migrate to Azure Database for PostgreSQL**: Azure recommends migrating Oracle databases to Azure Database for PostgreSQL Flexible Server as it provides better cost-effectiveness and performance. Create a managed PostgreSQL Flexible Server database in Azure and choose the appropriate pricing tier based on your application's requirements.\n\n * **Use migration tools**: Utilize the Azure Database Migration Service (DMS) or third-party tools to migrate your Oracle database schema and data to PostgreSQL. Consider using ora2pg or similar tools to convert Oracle-specific SQL to PostgreSQL-compatible SQL.\n\n * **Update database drivers and connection strings**: Replace Oracle JDBC drivers with PostgreSQL drivers in your Java application. Update connection strings from Oracle format (jdbc:oracle:thin:) to PostgreSQL format (jdbc:postgresql:).\n\n * **Review and convert Oracle-specific code**: Identify and convert Oracle-specific SQL functions, stored procedures, and PL/SQL code to PostgreSQL equivalents. Pay attention to data types, syntax differences, and built-in functions.\n\n * Enable **monitoring and diagnostics**: Utilize Azure Monitor to gain insights into the performance and health of your Java application and the underlying PostgreSQL database. Set up metrics, alerts, and log analytics to proactively identify and resolve issues.\n\n * Implement **security** measures: Apply security best practices to protect your Java application and the PostgreSQL database. This includes implementing authentication and authorization mechanisms with passwordless connections and leveraging Microsoft Defender for Cloud for threat detection and vulnerability assessments.\n\n * **Backup** your data: Azure Database for PostgreSQL provides automated backups by default. You can configure the retention period for backups based on your requirements. You can also enable geo-redundant backups, if needed, to enhance data durability and availability.", + "title": "Oracle database found", + "severity": "potential", + "effort": 8, + "links": [ + { + "url": "https://learn.microsoft.com/azure/postgresql", + "title": "Azure Database for PostgreSQL documentation" + }, + { + "url": "https://learn.microsoft.com/azure/postgresql/migrate/how-to-migrate-oracle-ora2pg", + "title": "Oracle to PostgreSQL migration guide" + }, + { + "url": "https://learn.microsoft.com/azure/dms", + "title": "Azure Database Migration Service documentation" + }, + { + "url": "https://learn.microsoft.com/azure/azure-monitor", + "title": "Azure Monitor documentation" + }, + { + "url": "https://learn.microsoft.com/azure/defender-for-cloud", + "title": "Microsoft Defender for Cloud" + } + ], + "labels": [ + "target=azure-appservice", + "target=azure-aks", + "target=azure-container-apps", + "source", + "domain=cloud-readiness", + "category=database-migration", + "database", + "oracle", + "os=windows", + "os=linux" + ] + }, + "azure-java-version-01000": { + "id": "azure-java-version-01000", + "description": "The application is using a Java version that has reached the end of support. It is strongly recommended to plan and execute a migration strategy to upgrade your application to a supported Java version.\nSupported Java versions receive long-term support (LTS) from the Java community, including bug fixes and updates. Migrating to a supported version provides you with a stable and well-maintained platform for your application.", + "title": "Java Version Has Reached the End of Support", + "severity": "mandatory", + "effort": 8, + "labels": [ + "target=azure-appservice", + "target=azure-aks", + "target=azure-container-apps", + "source", + "domain=java-upgrade", + "category=java-version-upgrade", + "version", + "os=windows", + "os=linux" + ] + }, + "azure-java-version-02000": { + "id": "azure-java-version-02000", + "description": "The application is not using the latest LTS Java version. It is recommended to consider upgrading to the latest LTS version to take advantage of the newest language features, performance improvements, and extended support timelines.\nUpgrading to the latest LTS version ensures your application benefits from the most recent security enhancements and a longer support lifecycle.", + "title": "Java Version is not the latest LTS", + "severity": "optional", + "effort": 8, + "labels": [ + "target=azure-appservice", + "target=azure-aks", + "target=azure-container-apps", + "source", + "domain=java-upgrade", + "category=java-version-upgrade", + "version", + "os=windows", + "os=linux" + ] + }, + "azure-password-01000": { + "id": "azure-password-01000", + "description": "Using clear passwords in property files is a security risk, as they can be easily compromised if the files are accessed by unauthorized individuals. To enhance the security of your application, it is recommended to employ secure credential management practices.\n\n * **Azure Key Vault**: Utilize Azure Key Vault to securely store and manage your application's passwords and other sensitive credentials. Azure Key Vault provides a centralized and highly secure location for storing secrets, keys, and certificates.\n\n * **Passwordless connections**: You can provide an additional layer of security and convenience for accessing resources in Azure by eliminating the need for passwords. This way you can reduce the risk of password-related vulnerabilities, such as weak passwords or password theft.", + "title": "Password found in configuration file", + "severity": "potential", + "effort": 3, + "links": [ + { + "url": "https://learn.microsoft.com/azure/key-vault", + "title": "Azure Key Vault documentation" + }, + { + "url": "https://learn.microsoft.com/azure/developer/intro/passwordless-overview", + "title": "Passwordless connections for Azure services" + }, + { + "url": "https://learn.microsoft.com/azure/developer/java/migration/migrate-spring-boot-to-azure-container-apps#inventory-configuration-sources-and-secrets", + "title": "Password found in configuration file" + }, + { + "url": "https://docs.microsoft.com/azure/developer/java/spring-framework/configure-spring-boot-starter-java-app-with-azure-key-vault", + "title": "Read a secret from Azure Key Vault in a Spring Boot application" + }, + { + "url": "https://search.maven.org/artifact/com.azure.spring/azure-spring-boot-starter-keyvault-secrets", + "title": "Azure Spring Boot Starter for Azure Key Vault Secrets" + } + ], + "labels": [ + "source", + "target=azure-appservice", + "target=azure-aks", + "target=azure-container-apps", + "domain=cloud-readiness", + "category=local-credential", + "password", + "security", + "os=windows", + "os=linux" + ] + }, + "jakarta-database-00002": { + "id": "jakarta-database-00002", + "description": "The application depends on **Jakarta Persistence (JPA)** APIs (`jakarta.persistence.*` or legacy `javax.persistence.*`), which are used for object-relational mapping (ORM) and database interaction in Jakarta EE or Java EE applications.\n\nWhen migrating to Azure:\n- Ensure that the database connection, JPA provider (e.g., Hibernate, EclipseLink), and dialect are compatible with your target Azure database service.\n- Recommended database services include **Azure Database for PostgreSQL**, **Azure Database for MySQL**, or **Azure SQL Database**.\n- For containerized deployments, these JPA-based applications can run on **Azure Kubernetes Service (AKS)** or **Azure App Service for Linux/Windows**.\n- If using **Spring Data JPA**, verify that connection pool settings and environment variables are properly configured for the cloud environment.\n- Consider leveraging **Azure Key Vault** for secure storage of database credentials and connection strings.", + "title": "Detects usage of Jakarta Persistence (JPA) APIs", + "severity": "potential", + "effort": 5, + "links": [ + { + "url": "https://jakarta.ee/specifications/persistence/", + "title": "Jakarta Persistence Specification" + }, + { + "url": "https://learn.microsoft.com/en-us/azure/architecture/guide/technology-choices/data-stores-getting-started#common-database-scenarios", + "title": "Prepare to choose a data store in Azure" + } + ], + "labels": [ + "source=java", + "source=java-ee", + "target=azure-aks", + "target=azure-container-apps", + "target=azure-appservice", + "domain=cloud-readiness", + "category=jakarta-migration", + "os=windows", + "os=linux" + ] + }, + "spring-boot-to-azure-port-01000": { + "id": "spring-boot-to-azure-port-01000", + "description": "The application is setting the server port. To migrate a Java application that sets the server port to Azure Container Apps:\n\n * **Azure Container Apps allows you to expose port according to your Azure Container Apps resource configuration. For instance, a Spring Boot application listens to port of 8080 by default, but it can be set with server.port or environment variable SERVER_PORT as you need.", + "title": "Server port configuration found", + "severity": "potential", + "effort": 1, + "links": [ + { + "url": "https://learn.microsoft.com/azure/developer/java/migration/migrate-spring-boot-to-azure-container-apps#identify-any-clients-relying-on-a-non-standard-port", + "title": "Identify any clients relying on a non-standard port" + } + ], + "labels": [ + "source=springboot", + "target=azure-aks", + "target=azure-appservice", + "target=azure-container-apps", + "domain=cloud-readiness", + "category=spring-migration", + "port", + "server port", + "os=windows", + "os=linux" + ] + }, + "spring-boot-to-azure-restricted-config-01000": { + "id": "spring-boot-to-azure-restricted-config-01000", + "description": "The application uses restricted configurations for Azure Container Apps.\n These properties can be automatically injected into your application environment by Azure Container Apps to access managed Config Server and managed Eureka Server.\n Please remove them from your application, including configuration files, config server files, command line parameters, Java system attributes, and environment variables.\n\n If configured in **configuration files**: they will be ignored and overrided by Azure Container Apps.\n \n If configured in **Config Server files**, **command line parameters**, **Java system attribute**, **environment variable**: they need to be removed or you might experience conflicts and unexpected behavior.", + "title": "Restricted configurations found", + "severity": "potential", + "effort": 2, + "links": [ + { + "url": "https://learn.microsoft.com/azure/developer/java/migration/migrate-spring-cloud-to-azure-container-apps#remove-restricted-configurations", + "title": "Migrate Spring Boot applications to Azure Container Apps - Remove restricted configurations" + }, + { + "url": "https://learn.microsoft.com/azure/container-apps/java-config-server?tabs=azure-cli", + "title": "Connect to a managed Config Server for Spring in Azure Container Apps" + }, + { + "url": "https://learn.microsoft.com/azure/container-apps/java-eureka-server?tabs=azure-cli", + "title": "Connect to a managed Eureka Server for Spring in Azure Container Apps" + } + ], + "labels": [ + "target=azure-container-apps", + "source=springboot", + "domain=cloud-readiness", + "category=spring-migration", + "os=windows", + "os=linux" + ] + }, + "spring-boot-to-azure-spring-boot-version-01000": { + "id": "spring-boot-to-azure-spring-boot-version-01000", + "description": "The application is using a Spring Boot version that has reached its End of OSS Support.\nWith the officially supported new versions from Spring, you can get the best experience. Here are some steps you can take to update your application to the latest version of Spring Boot:\n\n* Choose a **supported Spring Boot version**: Check out Spring Boot Support Versions and determine the most suitable supported Spring Boot version.\n\n* **Update Spring Boot version**: Update the Spring Boot version of your application. There are automated tools like Rewrite to help you with the migration.\n\n* **Address code compatibility**: Review your application's codebase for any potential compatibility issues with the target Spring Boot version. Update deprecated APIs or features, address any language or library changes, and ensure that your code follows best practices and standards.\n\n* **Test thoroughly**: Execute a comprehensive testing process to verify the compatibility and functionality of your application with the new Spring Boot version. Perform unit tests, integration tests, and system tests to validate that all components and dependencies work as expected.", + "title": "Spring Boot Version Has Reached the End of OSS Support", + "severity": "mandatory", + "effort": 8, + "links": [ + { + "url": "https://learn.microsoft.com/azure/developer/java/migration/migrate-spring-boot-to-azure-container-apps", + "title": "Migrate Spring Boot applications to Azure Container Apps" + }, + { + "url": "https://learn.microsoft.com/azure/container-apps/java-microservice-get-started?tabs=azure-cli", + "title": "Launch your first Java microservice application with managed Java components in Azure Container Apps" + }, + { + "url": "https://spring.io/projects/spring-boot/#support", + "title": "Spring Boot Supported Versions" + }, + { + "url": "https://github.com/spring-projects/spring-boot/wiki/Supported-Versions", + "title": "Spring Boot Support Policy" + } + ], + "labels": [ + "source=springboot", + "target=azure-appservice", + "target=azure-aks", + "target=azure-container-apps", + "domain=java-upgrade", + "category=framework-upgrade", + "version", + "os=windows", + "os=linux" + ] + }, + "spring-framework-version-01000": { + "id": "spring-framework-version-01000", + "description": "Your application is using a Spring Framework version that has reached its End of OSS Support.\nUpgrading to a supported version ensures better performance, security, and compatibility with modern tools.\n 1. Pick a Supported Version: Review the Spring Framework support policy and choose an actively supported version.\n 2. Update Your Project: Change the Spring Framework version in your pom.xml or build.gradle.\n 3. Fix Compatibility Issues: Update deprecated code, replace removed features, and ensure dependencies are compatible with the new Spring Framework version.\n 4. Thoroughly Test: Run unit, integration, and end-to-end tests to make sure everything still works after the upgrade.", + "title": "Spring Framework Version Has Reached the End of OSS Support", + "severity": "mandatory", + "effort": 8, + "links": [ + { + "url": "https://spring.io/projects/spring-framework#support", + "title": "Spring Framework Supported Versions" + }, + { + "url": "https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Versions", + "title": "Spring Framework Support Policy" + } + ], + "labels": [ + "source=spring", + "target=azure-appservice", + "target=azure-aks", + "target=azure-container-apps", + "domain=java-upgrade", + "category=framework-upgrade", + "version", + "os=windows", + "os=linux" + ] + } + } +} \ No newline at end of file diff --git a/.github/modernize/assessment/reports/report-20260521061104/report.md b/.github/modernize/assessment/reports/report-20260521061104/report.md new file mode 100644 index 000000000..ab0a3c7c9 --- /dev/null +++ b/.github/modernize/assessment/reports/report-20260521061104/report.md @@ -0,0 +1,137 @@ +# photo-album + +## Summary + +| Metric | Value | +|--------|-------| +| Total Issues | 9 | +| Mandatory Blockers | 3 | +| Potential Issues | 5 | + +## Application Information + +| Property | Value | +|----------|-------| +| Language | Java, Python | +| Frameworks | Spring Boot, Spring | +| Build tools | Maven | +| JDK version | 1.8 | + +## Cloud Readiness Issues + +| Issue Name | Criticality | Story Points | Occurrences | +|------------|-------------|--------------|-------------| +| Oracle database found | Potential | 8 | [6](#Oracle_database_found) | +| Password found in configuration file | Potential | 3 | [3](#Password_found_in_configuration_file) | +| Server port configuration found | Potential | 1 | [2](#Server_port_configuration_found) | +| Restricted configurations found | Potential | 2 | [2](#Restricted_configurations_found) | +| Detects usage of Jakarta Persistence (JPA) APIs | Potential | 5 | [1](#Detects_usage_of_Jakarta_Persistence_JPA_APIs) | + +### Issue Details + +
+Oracle database found — affected files + +- `pom.xml (line 52)` +- `docker-compose.yml (line 32)` +- `src/main/resources/application-docker.properties (line 2)` +- `src/main/resources/application.properties (line 10)` +- `src/main/resources/application-docker.properties (line 5)` +- `src/main/resources/application.properties (line 13)` + +
+ +
+Password found in configuration file — affected files + +- `src/test/resources/application-test.properties (line 5)` +- `src/main/resources/application-docker.properties (line 4)` +- `src/main/resources/application.properties (line 12)` + +
+ +
+Server port configuration found — affected files + +- `src/main/resources/application-docker.properties (line 24)` +- `src/main/resources/application.properties (line 2)` + +
+ +
+Restricted configurations found — affected files + +- `src/main/resources/application-docker.properties (line 24)` +- `src/main/resources/application.properties (line 2)` + +
+ +
+Detects usage of Jakarta Persistence (JPA) APIs — affected files + +- `pom.xml (line 46)` + +
+ +## Upgrade Issues + +| Issue Name | Criticality | Story Points | Occurrences | +|------------|-------------|--------------|-------------| +| Spring Boot Version Has Reached the End of OSS Support | Mandatory | 8 | [7](#Spring_Boot_Version_Has_Reached_the_End_of_OSS_Support) | +| Spring Framework Version Has Reached the End of OSS Support | Mandatory | 8 | [3](#Spring_Framework_Version_Has_Reached_the_End_of_OSS_Support) | +| Java Version Has Reached the End of Support | Mandatory | 8 | [1](#Java_Version_Has_Reached_the_End_of_Support) | +| Java Version is not the latest LTS | Optional | 8 | [2](#Java_Version_is_not_the_latest_LTS) | + +### Issue Details + +
+Spring Boot Version Has Reached the End of OSS Support — affected files + +- `pom.xml (line 40)` +- `pom.xml (line 78)` +- `pom.xml (line 34)` +- `pom.xml (line 72)` +- `pom.xml (line 59)` +- `pom.xml (line 92)` +- `pom.xml (line 46)` + +
+ +
+Spring Framework Version Has Reached the End of OSS Support — affected files + +- `pom.xml (line 78)` +- `pom.xml (line 34)` +- `pom.xml (line 46)` + +
+ +
+Java Version Has Reached the End of Support — affected files + +- `pom.xml (line 24)` + +
+ +
+Java Version is not the latest LTS — affected files + +- `pom.xml (line 25)` +- `pom.xml (line 26)` + +
+ +--- + +## Codebase Insights + +> **Note:** These documents are generated by AI and may contain inaccuracies or incomplete information. Please review carefully. + +1. **[Architecture Diagram](facts/architecture-diagram.md)** — Understand the big picture: system layers and component relationships +2. **[Dependency Map](facts/dependency-map.md)** — Know what the project depends on and where the risks are +3. **[API & Service Contracts](facts/api-service-contracts.md)** — See how services communicate and what contracts they expose +4. **[Data Architecture](facts/data-architecture.md)** — Explore data models, storage, and data flow patterns +5. **[Configuration Inventory](facts/configuration-inventory.md)** — Review how the application is configured across environments +6. **[Business Workflows](facts/business-workflows.md)** — Trace end-to-end business processes and domain logic + +[Share feedback](https://aka.ms/ghcp-appmod/feedback) diff --git a/.github/modernize/ccacontext/.ccaskills b/.github/modernize/ccacontext/.ccaskills deleted file mode 100644 index f1cb694f5..000000000 --- a/.github/modernize/ccacontext/.ccaskills +++ /dev/null @@ -1,68 +0,0 @@ -assessment -create-dotnet-upgrade-plan -create-java-upgrade-plan -create-modernization-plan -execute-java-upgrade-task -execute-modernization-task -execute-upgrade-task -integration-tests -reconcile-tasks-with-plan -rulebook-create -validate-assessment-rulebook-compliance -validate-rulebook-compliance -validate-rulebook-evidence -api-service-contracts -architecture-diagram -business-workflows -configuration-inventory -cve-known-vulnerabilities -cwe-code-quality -cwe-concurrency-synchronization -cwe-credentials-secrets -cwe-file-path-security -cwe-injection-attacks -cwe-memory-safety -data-architecture -dependency-map -dotnet-upgrade-assessment -fact-application-name -fact-application-port -fact-application-type -fact-architecture-pattern -fact-base-image -fact-communication-protocols -fact-compliance-requirements -fact-container-engine -fact-container-version -fact-data-classification -fact-embedded-language-usage -fact-environment-variables -fact-external-dependencies -fact-external-services -fact-hardware-requirements -fact-health-checks -fact-image-layers -fact-image-size -fact-language-dependencies -fact-licensing-information -fact-multi-stage-build -fact-network-settings -fact-operating-system -fact-orchestration-tool -fact-profile-settings -fact-resource-limits -fact-runtime-environment -fact-security-implementation -fact-service-definition -fact-servlet-container -fact-startup-instrumentation -fact-system-packages -fact-testing-framework -fact-version-information -fact-volume-mounts -fact-xml-configs -generate-report-html -generate-report-md -rearchitect -repository-dependency-graph -security-assessment-merge diff --git a/.github/modernize/ccacontext/cleanup.sh b/.github/modernize/ccacontext/cleanup.sh deleted file mode 100644 index 7a0ed8bef..000000000 --- a/.github/modernize/ccacontext/cleanup.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash -# Removes CCA-copied skill directories listed in .ccaskills -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -SKILLS_DIR="$(cd "$SCRIPT_DIR/../../skills" && pwd)" -SKILLS_DIR_REAL="$(cd "$SKILLS_DIR" && pwd -P)" -CCASKILLS="$SCRIPT_DIR/.ccaskills" - -if [ ! -f "$CCASKILLS" ]; then - echo "No .ccaskills file found, nothing to clean." - exit 0 -fi - -while IFS= read -r skill; do - skill="${skill%$'\r'}" - [ -z "$skill" ] && continue - - case "$skill" in - /*|*\\*|*/*|*..*) - echo "Skipping invalid skill entry: $skill" - continue - ;; - esac - - target="$SKILLS_DIR/$skill" - if [ -d "$target" ]; then - target_real="$(cd "$target" 2>/dev/null && pwd -P)" - if [ -z "$target_real" ]; then - echo "Skipping unresolved path: $skill" - continue - fi - - case "$target_real" in - "$SKILLS_DIR_REAL"/*) - rm -rf -- "$target_real" - echo "Removed: $skill" - ;; - *) - echo "Skipping out-of-scope path: $skill" - ;; - esac - fi -done < "$CCASKILLS" - -rm -f "$CCASKILLS" - -# Stage removal of .github/skills and ccacontext so the PR diff doesn't include them -git rm -rf --quiet .github/skills 2>/dev/null || true -git rm -rf --quiet .github/modernize/ccacontext 2>/dev/null || true - -echo "Cleanup complete." diff --git a/.github/skills/api-service-contracts/SKILL.md b/.github/skills/api-service-contracts/SKILL.md deleted file mode 100644 index bcbbe5e28..000000000 --- a/.github/skills/api-service-contracts/SKILL.md +++ /dev/null @@ -1,210 +0,0 @@ ---- -name: api-service-contracts -description: Generate API and service communication contracts with sequence diagram ---- - -# API & Service Communication Contracts - -Analyze the project to document all services, API endpoints, communication patterns (sync/async), DTOs, and retry/circuit-breaker policies. Generate a Mermaid sequence diagram showing the primary request flow across services. Save to `.github/modernize/assessment/engines/api-service-contracts.md`. - -## Input Parameters - -- `workspace-path` (optional): Path to the project to analyze (defaults to current directory) - -## Scope Boundaries — Avoid Redundancy with Other Skills - -This skill is part of a set of four complementary assessment skills. To avoid content duplication across their output documents, observe these scope rules: - -- **Introduction**: Write a 1-2 sentence intro focused on the API surface (number of endpoints, communication style). Do NOT restate the application's technology stack, database options, or architecture type — those are covered by other skills. -- **Entity fields and persistence details** are owned by the `data-architecture` skill. In the DTOs & Contracts section, list entity/DTO **class names** and their role in the API contract (request type, response type, immutability). Do NOT reproduce full field lists, ORM annotations (cascade, fetch strategy), or table names — reference `data-architecture.md` instead. -- **Validation rules** (e.g., `@NotBlank`, custom validators) are owned by the `business-workflows` skill. Mention validation only when it affects the API contract (e.g., "returns 400 if validation fails"). Do NOT enumerate individual field constraints. -- **Caching implementation details** (provider, TTL, configuration class) are owned by the `data-architecture` skill. In the sequence diagram, you may show cache hit/miss behavior, but do NOT repeat the cache provider name, configuration details, or rationale. -- **Configuration properties and profiles** (e.g., `spring.jpa.*`, database profiles) are owned by the `configuration-inventory` skill. Do NOT list property keys/values. -- **Startup dependency chain details** (readiness probes, K8s manifests, dockerize) are owned by the `configuration-inventory` skill. Mention startup order only if it directly affects API availability. Do NOT repeat probe paths or wait mechanisms. - -## Execution Steps - -### Step 1: Generate Service Catalog Section - -Identify all independently deployable services/modules and produce the complete `## Service Catalog` section: - -- Multi-module builds: Maven modules (`pom.xml` ``), Gradle subprojects (`settings.gradle`), .NET solutions (`.sln` → `.csproj` projects), monorepo workspaces (`package.json` workspaces) -- Docker Compose services (`docker-compose.yml` service definitions) — note third-party containers vs source-built services -- Kubernetes deployments, Helm charts, or IaC definitions - -For each service extract: -- Service name and Maven module / project name -- Port number (from config files, `docker-compose.yml`, or `application.properties`/`appsettings.json`) -- Category: **API Layer** (gateways, BFFs), **Business** (domain services), **Infrastructure** (config, discovery, admin), **Observability** (tracing, metrics, dashboards) -- Purpose (one-line description) -- Key framework dependencies (from `pom.xml`, `.csproj`, `package.json`) - -### Step 2: Generate API Endpoints Inventory Section - -Scan source code for API endpoint definitions and produce the complete `## API Endpoints Inventory` section: - -- Java (Spring): `@RestController`, `@Controller`, `@GetMapping`, `@PostMapping`, `@PutMapping`, `@DeleteMapping`, `@RequestMapping` -- Java (Jakarta EE): `@Path`, `@GET`, `@POST`, `@PUT`, `@DELETE` (JAX-RS) -- .NET (ASP.NET Core): `[ApiController]`, `[HttpGet]`, `[HttpPost]`, `[HttpPut]`, `[HttpDelete]`, `[Route]` -- JavaScript/TypeScript: Express routes (`app.get`, `app.post`, `router.get`), Fastify routes, NestJS decorators (`@Get`, `@Post`) - -For each endpoint extract: -- HTTP method (GET, POST, PUT, DELETE, PATCH) -- URL path (including path parameters) -- Request type (body/query/path parameters, DTO class name) -- Response type (DTO class name, status codes) -- API versioning scheme if present (URL path, header, query parameter) -- Which service/controller it belongs to - -### Step 3: Generate Management & Observability Endpoints Section - -Identify management and observability endpoints and produce the complete `## Management & Observability Endpoints` section: - -- Spring Boot Actuator endpoints (`/actuator/health`, `/actuator/info`, `/actuator/metrics`, `/actuator/prometheus`) -- .NET health checks (`/health`, `/healthz`), Swagger UI (`/swagger`) -- Custom metrics annotations: `@Timed` (Micrometer), `[Meter]`, custom metric registrations — note the metric name and which service exposes it - -### Step 4: Generate DTOs & Contracts Section - -Analyze DTO and contract definitions and produce the complete `## DTOs & Contracts` section: - -- Find DTO / request / response model classes (records, POJOs, C# records/classes). List class names and their API role (request body, response, path/query param). Do NOT reproduce full field lists or ORM annotations — those belong in `data-architecture.md`. -- **Distinguish gateway-level DTOs** (aggregation/composition models that combine data from multiple services) from **service-level domain entities** (owned by a single service) -- Note which DTOs are immutable (Lombok `@Value`, Java records, C# records, frozen data classes) -- Identify OpenAPI/Swagger specifications (`openapi.yaml`, `swagger.json`, Springdoc/Swashbuckle annotations) -- Check for protobuf schemas (`.proto` files) or GraphQL schemas -- Note serialization configuration (Jackson, System.Text.Json, custom serializers) - -### Step 5: Generate Communication Patterns Section - -Identify inter-service and intra-service communication and produce the complete `## Communication Patterns` section: - -- **Synchronous**: REST (HttpClient, RestTemplate, WebClient, Feign), gRPC, direct method calls -- **Asynchronous**: Message queues (Kafka, RabbitMQ, Azure Service Bus, SQS), event-driven patterns, pub/sub -- **Resilience patterns**: Circuit breaker (Resilience4j, Polly, Spring Retry), retry policies, timeout configuration, bulkhead patterns — note specific timeout values and fallback behavior -- **Service discovery**: Eureka, Consul, Kubernetes DNS, Azure Service Discovery — note whether services register by logical name or hardcoded URL -- **API gateway**: Spring Cloud Gateway, Ocelot, Kong, custom gateway patterns -- **Gateway aggregation/composition**: Document how the gateway combines responses from multiple backend services (e.g., fetching owner details from one service and visit history from another, then merging them into a single response). Note the composition logic and fallback behavior when a downstream service is unavailable. -- **Client-side load balancing**: Spring Cloud LoadBalancer, Ribbon, or framework-provided balancing -- **Startup dependency chain**: Briefly note the service startup order if it affects API availability. For full details (probes, wait mechanisms, timeouts), refer to `configuration-inventory.md`. -- **Security posture**: Note whether transport security (HTTPS/TLS), authentication (JWT, OAuth2, Basic Auth, Spring Security), or authorization (RBAC, `@PreAuthorize`, role checks) are implemented at the API level. If absent, state it explicitly — e.g., "No authentication or TLS configured; all endpoints are publicly accessible with no authorization checks." Do NOT duplicate CWE security scan findings; focus only on presence or absence at the API contract level. - -### Step 6: Generate Service Technology Matrix Section - -For each service, identify which cross-cutting capabilities it uses and produce the complete `## Service Technology Matrix` section: - -- Web framework (MVC, Reactive/WebFlux, Minimal API) -- Data access (JPA, EF Core, Mongoose, etc.) -- Service discovery (client, server, or none) -- Gateway functionality -- Actuator/health checks -- Caching layer -- Metrics export (Prometheus, Application Insights, etc.) - -### Step 7: Generate Service Communication Sequence Section - -Create a **Mermaid `sequenceDiagram`** and produce the complete `## Service Communication Sequence` section: -- Show key actors: Client, API Gateway (if present), Controllers, Services, External Services, Message Brokers -- Annotate synchronous calls with solid arrows and asynchronous calls with dashed arrows -- Include request/response types where relevant -- Show error handling paths for critical flows (circuit breaker, retry) -- For gateway aggregation flows, show how multiple downstream calls are composed - -Example: - -~~~mermaid -sequenceDiagram - participant Client - participant Gateway as "API Gateway" - participant CustSvc as "Customers Service" - participant VisitSvc as "Visits Service" - participant DB as "Database" - - Client->>Gateway: GET /api/gateway/owners/1 - Gateway->>CustSvc: GET /owners/1 - CustSvc->>DB: findById(1) - DB-->>CustSvc: Owner + Pets - CustSvc-->>Gateway: OwnerDetails(pets=[Pet1,Pet2]) - Gateway->>VisitSvc: GET /pets/visits?petId=1,2 - alt Visits Service Available - VisitSvc->>DB: findByPetIdIn([1,2]) - DB-->>VisitSvc: Visits list - VisitSvc-->>Gateway: Visits(items=[...]) - else Circuit Breaker Open - Gateway-->>Gateway: Fallback - empty visits - end - Gateway->>Gateway: Merge visits into pets - Gateway-->>Client: 200 OwnerDetails + Visits -~~~ - -### Step 8: Save Output - -Save to `.github/modernize/assessment/engines/api-service-contracts.md` with this exact structure: - -``` -# API & Service Communication Contracts - -A brief introduction (1-2 sentences) summarizing the API surface and communication patterns found. - -## Service Catalog - -[Table: Service | Port | Category | Purpose] - -## API Endpoints Inventory - -[Table: Service | Method | Path | Request Type | Response Type] - -## Management & Observability Endpoints - -[Table: Service | Endpoint | Custom Metrics (if any)] - -## DTOs & Contracts - -[Description of gateway-level DTOs vs service-level entities, immutability, serialization] - -## Communication Patterns - -[Description of sync/async patterns, gateway aggregation/composition logic, circuit breaker/retry policies with timeout values, service discovery, startup dependency chain, and security posture (authentication/authorization/TLS — or explicit statement that none is configured)] - -## Service Technology Matrix - -[Table: Service | Web | Data Access | Discovery | Gateway | Actuator | Cache | Metrics] - -## Service Communication Sequence - -< Mermaid sequenceDiagram here > -``` - -## Scaling Rules - -- If the project has **more than 30 endpoints**, group by service/controller and show representative endpoints per group -- Keep the sequence diagram under **40 participants and messages** to ensure readability and GitHub rendering compatibility -- For multi-module projects, focus on inter-module communication in the sequence diagram and list all endpoints in the table -- Aggregate similar endpoints (e.g., CRUD operations on the same resource) into one table row if needed for brevity -- For the service technology matrix, use checkmarks or short labels; omit columns where no service uses the capability - -## Mermaid Syntax Rules - -- Use `sequenceDiagram` -- Avoid special characters (`@`, `#`, `$`, `%`, `&`) in participant labels — use plain text or quoted labels -- Use `->>` for synchronous calls and `-->>` for responses/async messages -- Use `participant` with alias syntax for readable labels: `participant Svc as "OrderService"` -- Use `alt`/`else`/`end` blocks to show circuit breaker fallback paths -- Do not use backticks inside node labels - -## Error Handling - -- **Unsupported project type**: Output a single line: `> ERROR: Unsupported project type. This skill supports Java, .NET, JavaScript, and TypeScript projects only.` -- **No API endpoints found**: Output: `> ERROR: No recognized API endpoints found at {workspace-path}. Verify the path is correct.` -- **Insufficient info**: Generate a best-effort document from available data. Add a note: `> Note: Some endpoints or communication patterns could not be fully identified.` - -## Success Criteria - -- Service catalog table lists all discovered services with ports, categories, and purposes -- API endpoints table lists all discovered endpoints with HTTP method, path, and types -- Management/observability endpoints are cataloged with custom metric names -- Gateway aggregation/composition patterns are documented with fallback behavior -- Service technology matrix shows per-service capabilities -- Communication patterns section describes sync/async patterns, resilience policies, and security posture (authentication, authorization, TLS — explicitly stating if none is configured) -- Mermaid sequence diagram renders correctly showing primary request flow with aggregation and fallback -- File saved to `.github/modernize/assessment/engines/api-service-contracts.md` diff --git a/.github/skills/architecture-diagram/SKILL.md b/.github/skills/architecture-diagram/SKILL.md deleted file mode 100644 index e74695943..000000000 --- a/.github/skills/architecture-diagram/SKILL.md +++ /dev/null @@ -1,190 +0,0 @@ ---- -name: architecture-diagram -description: Generate architecture diagram with component relationship details from project analysis ---- - -# Architecture Diagram - -This skill generates a two-layer architecture visualization: a high-level application architecture diagram and a detailed component relationship diagram. Produce both in a single pass and save to `.github/modernize/assessment/engines/architecture-diagram.md`. - -## Input Parameters - -- `workspace-path` (optional): Path to the project to analyze (defaults to current directory) - -## Execution Steps - -### Step 1: Generate Application Architecture Section - -Analyze the project and produce the complete `## Application Architecture` section in one pass: - -**Analysis:** -- Examine build files (Java: pom.xml, build.gradle; .NET: *.csproj, *.sln; JS/TS: package.json, tsconfig.json) -- Review configuration files (application.properties, appsettings.json, .env, database/API configs) -- Scan key source files to extract: framework, major dependencies, data access patterns, external integrations, technology stack -- Identify application layers (UI, Business Logic, Data Access), data storage technologies, and external service dependencies - -**Diagram — Mermaid `flowchart TD`:** -- Application layers with technology info (use `subgraph` for grouping) -- Data storage components (specific names like "PostgreSQL", "Redis") -- External service integrations -- Data flow with descriptive arrow labels - -**Do NOT include**: individual classes/methods or migration directions. - -Example: - -~~~mermaid -flowchart TD - subgraph Client["Client Layer"] - Browser["Web Browser"] - end - subgraph App["Application Layer - Spring Boot 2.7"] - Web["Spring MVC + Thymeleaf"] - Security["Spring Security"] - Service["Business Services"] - end - subgraph Data["Data Layer"] - JPA["Spring Data JPA"] - DB[("PostgreSQL 14")] - Cache[("Redis")] - end - subgraph External["External Services"] - SMTP["SMTP Email Service"] - S3["AWS S3 Storage"] - end - - Browser -->|"HTTP requests"| Web - Web --> Security -->|"authorized"| Service - Service -->|"CRUD operations"| JPA - JPA -->|"SQL queries"| DB - Service -->|"session cache"| Cache - Service -->|"send email"| SMTP - Service -->|"file upload"| S3 -~~~ - -**Textual explanations (write immediately after the diagram):** -- **Technology Stack Summary table**: Layer | Technology | Version | Purpose (e.g., Presentation | ASP.NET MVC 5 | 5.2.7 | Server-side web framework) -- **Data Storage & External Services**: A short paragraph describing what databases, caches, message brokers, or external APIs are used and how they fit into the architecture -- **Key Architectural Decisions**: 1-3 bullet points on notable patterns (e.g., "Uses repository pattern with EF6 for data access", "Autofac provides DI with module-based registration") - -### Step 2: Generate Component Relationships Section - -Analyze component interactions and produce the complete `## Component Relationships` section in one pass: - -**Analysis:** -- Identify key component types by framework conventions: - - Java (Spring): Controllers, Services, Repositories, Configurations, Entities, DTOs, Listeners, Filters - - Java (Jakarta EE): Servlets, EJBs, CDI Beans, JPA Entities, JAX-RS Resources - - .NET (ASP.NET Core): Controllers, Services, Middleware, DbContext, Entities, Hubs, Filters - - .NET (Blazor/MVC): Pages, Components, ViewModels - - JavaScript/TypeScript (Node.js): Routes, Controllers, Services, Middleware, Models - - JavaScript/TypeScript (React/Angular/Vue): Components, Hooks, Services, Stores, Pages -- Trace dependency injection (constructor/field injection) -- Map communication patterns (REST, gRPC, message queues, events) -- Map data access patterns (service-to-repository, DbContext usage) -- Detect cross-cutting concerns (middleware, interceptors, filters) - -**Diagram — Mermaid `flowchart LR`:** -- Components grouped by architectural layer using `subgraph` (Presentation, Business Logic, Data Access, Infrastructure) -- Interaction arrows with brief labels -- Cross-cutting concerns - -**Do NOT include**: method signatures, private helpers, or external dependencies (covered by dependency-map skill). - -Example: - -~~~mermaid -flowchart LR - subgraph Presentation - UserCtrl["UserController"] - OrderCtrl["OrderController"] - end - subgraph Business["Business Logic"] - UserSvc["UserService"] - OrderSvc["OrderService"] - NotifSvc["NotificationService"] - end - subgraph DataAccess["Data Access"] - UserRepo["UserRepository"] - OrderRepo["OrderRepository"] - end - subgraph Infra["Infrastructure"] - AuthFilter["AuthenticationFilter"] - LogMiddleware["LoggingMiddleware"] - end - - UserCtrl -->|"delegates"| UserSvc - OrderCtrl -->|"delegates"| OrderSvc - OrderSvc -->|"lookups"| UserSvc - OrderSvc -->|"triggers"| NotifSvc - UserSvc -->|"queries"| UserRepo - OrderSvc -->|"queries"| OrderRepo - AuthFilter -.->|"intercepts"| UserCtrl - AuthFilter -.->|"intercepts"| OrderCtrl - LogMiddleware -.->|"wraps"| Presentation -~~~ - -**Textual explanation (write immediately after the diagram):** -- **Component Inventory table**: Component | Layer | Type | Responsibility (e.g., CatalogController | Presentation | MVC Controller | Handles catalog browsing and CRUD) - -### Step 3: Save Output - -Save the combined output to `.github/modernize/assessment/engines/architecture-diagram.md` with this exact structure: - -``` -# Architecture Diagram - -A brief introduction (1-2 sentences). - -## Application Architecture - -< Layer 1 Mermaid flowchart TD here > - -### Technology Stack Summary - -[Table: Layer | Technology | Version | Purpose] - -### Data Storage & External Services - -[Short paragraph on databases, caches, external APIs] - -### Key Architectural Decisions - -[1-3 bullet points on notable patterns] - -## Component Relationships - -< Layer 2 Mermaid flowchart LR here > - -### Component Inventory - -[Table: Component | Layer | Type | Responsibility] -``` - -## Scaling Rules - -- If the project has **more than 30 components**, aggregate by package/namespace (e.g., show `com.example.orders` as one node instead of listing every class) -- Keep each diagram under **40 nodes** to ensure readability and GitHub rendering compatibility -- For multi-module projects, focus on inter-module boundaries in Layer 1 and key components within the most important modules in Layer 2 - -## Mermaid Syntax Rules - -- Use `flowchart TD` for Layer 1 and `flowchart LR` for Layer 2 -- Avoid special characters (`@`, `#`, `$`, `%`, `&`) in node labels — use plain text -- Always quote arrow labels with double quotes: `-->|"label"|` -- Use `subgraph` for grouping, with a display name in quotes if it contains spaces -- Verify all node IDs are unique across the entire diagram - -## Error Handling - -- **Unsupported project type**: Output a single line: `> ERROR: Unsupported project type. This skill supports Java, .NET, JavaScript, and TypeScript projects only.` -- **No source code found**: Output: `> ERROR: No recognized source files found at {workspace-path}. Verify the path is correct.` -- **Insufficient info**: Generate a best-effort diagram from available data. Add a note inside the diagram: `Note["Some components could not be identified"]` - -## Success Criteria - -- Layer 1 Mermaid diagram renders correctly showing architecture with technology names, data storage, and external dependencies -- Layer 1 is accompanied by Technology Stack Summary table, Data Storage & External Services paragraph, and Key Architectural Decisions -- Layer 2 Mermaid diagram renders correctly showing component interactions grouped by architectural layer -- Layer 2 is accompanied by Component Inventory table -- File saved to `.github/modernize/assessment/engines/architecture-diagram.md` diff --git a/.github/skills/assessment/SKILL.md b/.github/skills/assessment/SKILL.md deleted file mode 100644 index a9c4cae77..000000000 --- a/.github/skills/assessment/SKILL.md +++ /dev/null @@ -1,173 +0,0 @@ ---- -name: assessment -description: Run application assessment for a single repository ---- - -# Application Assessment - -This skill performs application assessment for a single repository. It supports Java, .NET, and JavaScript/TypeScript projects. - -## Input Parameters - -- `workspace-path` (optional): Path to the project to assess. Defaults to the current directory (repository root) when not specified. All assessment outputs are written relative to this path (e.g. `{workspace-path}/.github/modernize/assessment/reports/report-{reportId}/report.json`). For a repository with multiple sub-projects, pass the sub-project directory path so that each sub-project's outputs are isolated. - -## When to Use This Skill - -Use this skill when you need to: - -- Assess a Java or .NET application for cloud readiness and migration issues -- Assess a JavaScript/TypeScript project for outdated dependencies and available updates -- Generate detailed assessment reports with issue analysis and recommendations -- Understand application dependencies, frameworks, and potential migration blockers - -## What This Skill Does - -This skill performs a simplified assessment workflow: - -1. **Check Project Type and Prerequisites**: - - **For Java projects**: Verify that MCP tools are available ('appmod-precheck-assessment' and 'appmod-run-assessment') - - If these MCP tools are not configured, return immediately with setup instructions - - **For .NET projects**: Check if .NET SDK is available - - No MCP tools required for .NET assessment - - **For JavaScript/TypeScript projects**: Check if Node.js and npm are available - - No MCP tools required for JS/TS assessment - -2. **Run Assessment**: - - **For Java projects**: Trigger AppCAT analysis via Assessment MCP server - - Uses 'appmod-precheck-assessment' and 'appmod-run-assessment' MCP tools - - Auto-detects project configuration within `{workspace-path}` - - **For .NET projects**: Install and run AppCAT directly - - Install: `dotnet tool update dotnet-appcat` - - Find all .csproj files under `{workspace-path}` - - Join project paths with semicolons: `projectPaths="project1.csproj;project2.csproj"` - - Run: `dotnet-appcat analyze $projectPaths --source Solution --target Any --serializer APPMODJSON --code --privacyMode Restricted --non-interactive --report {workspace-path}\.github\modernize\appcat\result\report.json` - - **For JavaScript/TypeScript projects**: Install and run npm-check-updates - - Install: `npm install -g npm-check-updates@19.6.3 --prefix {tool-install-dir}` - - Run: `ncu --format group --packageFile {workspace-path}/package.json` - - Save the output to `{workspace-path}/.github/modernize/assessment/js-assessment-report.md` - - Analyzes code for cloud migration issues or dependency updates - - Generates structured assessment data - -3. **Save Report to Versioned Directory (Java and .NET only)**: - - **For Java projects**: Search for `report.json` under `{workspace-path}/.github/modernize/appcat/result/` or `{workspace-path}/.github/appmod/appcat/result/` subdirectories - - **For .NET projects**: The report is at `{workspace-path}/.github/modernize/appcat/result/report.json` - - Read the generated `report.json` and extract the `metadata.analysisStartTime` field - - Format the timestamp as `yyyyMMddHHmmss` to produce the `reportId` (e.g. `2024-06-15T14:30:52Z` becomes `20240615143052`) - - Create the versioned directory: `mkdir -p {workspace-path}/.github/modernize/assessment/reports/report-{reportId}` - - Move the report to: `{workspace-path}/.github/modernize/assessment/reports/report-{reportId}/report.json` - - This versioned report should be included in the pull request - -## How to Use - -### Prerequisites - -**For Java projects**: -- MCP tools must be available: 'appmod-precheck-assessment' and 'appmod-run-assessment' -- If tools are not configured, the skill will return instructions for setup - -**For .NET projects**: -- .NET SDK must be installed -- No MCP tools required - appcat will be installed and run directly via .NET CLI -- The assessment will automatically install `dotnet-appcat` tool if not already present - -**For JavaScript/TypeScript projects**: -- Node.js and npm must be installed -- No MCP tools required - npm-check-updates will be installed and run directly via npm -- The assessment will automatically install `npm-check-updates` if not already present - -### Triggering Assessment - -Simply express the intent to assess the application. Example prompts: - -- "Assess the application" -- "Run assessment for this project" - -The assessment process automatically: -- Detects project language and framework within `{workspace-path}` -- **For Java**: Uses MCP tools to install and run AppCAT -- **For .NET**: Installs dotnet-appcat tool and runs analysis directly -- **For JavaScript/TypeScript**: Installs npm-check-updates and runs dependency analysis -- Executes comprehensive analysis -- **For Java**: Generates report at `{workspace-path}/.github/modernize/appcat/result/` or `{workspace-path}/.github/appmod/appcat/result/report.json` -- **For .NET**: Generates report at `{workspace-path}/.github/modernize/appcat/result/report.json` -- **For JavaScript/TypeScript**: Generates report at `{workspace-path}/.github/modernize/assessment/js-assessment-report.md` - -### Report Saving - -**For Java projects**: -1. Search for `report.json` files under `{workspace-path}/.github/modernize/appcat/result/` or `{workspace-path}/.github/appmod/appcat/result/` subdirectories -2. If multiple reports exist, identify the most recently modified one -3. Read the report and extract `metadata.analysisStartTime`, format as `yyyyMMddHHmmss` to get `reportId` -4. Move the report to `{workspace-path}/.github/modernize/assessment/reports/report-{reportId}/report.json` -5. Include this versioned report in the pull request - -**For .NET projects**: -1. Report is initially generated at `{workspace-path}/.github/modernize/appcat/result/report.json` -2. Read the report and extract `metadata.analysisStartTime`, format as `yyyyMMddHHmmss` to get `reportId` -3. Move the report to `{workspace-path}/.github/modernize/assessment/reports/report-{reportId}/report.json` -4. Include this versioned report in the pull request - -**For JavaScript/TypeScript projects**: -1. Report is directly generated at `{workspace-path}/.github/modernize/assessment/js-assessment-report.md` -2. Include this report in the pull request - -## Report Output Location - -Report location depends on project type: - -**For Java projects** (via MCP server): -- Initially stored under `{workspace-path}/.github/modernize/appcat/result/` or `{workspace-path}/.github/appmod/appcat/result/` subdirectories -- Saved to versioned directory: `{workspace-path}/.github/modernize/assessment/reports/report-{reportId}/report.json` - -Where `{reportId}` is derived from `metadata.analysisStartTime` in the report, formatted as `yyyyMMddHHmmss`. - -**For .NET projects** (direct execution): -- Initially generated at: `{workspace-path}/.github/modernize/appcat/result/report.json` -- Moved to versioned directory: `{workspace-path}/.github/modernize/assessment/reports/report-{reportId}/report.json` - -**For JavaScript/TypeScript projects** (direct execution): -- Directly generated at: `{workspace-path}/.github/modernize/assessment/js-assessment-report.md` - -## Success Criteria - -Assessment is complete when: -- ✅ **For Java**: MCP server is available (or clear instructions provided if not) -- ✅ **For .NET**: .NET SDK is available and dotnet-appcat tool is installed -- ✅ **For JavaScript/TypeScript**: Node.js and npm are available and npm-check-updates is installed -- ✅ AppCAT analysis executes without errors (Java/.NET) or ncu analysis executes without errors (JS/TS) -- ✅ **For Java and .NET**: Report generated at `{workspace-path}/.github/modernize/assessment/reports/report-{reportId}/report.json` -- ✅ **For JavaScript/TypeScript**: Report generated at `{workspace-path}/.github/modernize/assessment/js-assessment-report.md` -- ✅ Report metadata includes assessment tool version, timestamp, and configuration - -## Troubleshooting - -**Prerequisites Not Met**: -- **For Java**: Verify MCP tools are available ('appmod-precheck-assessment' and 'appmod-run-assessment') - - Return immediately with setup instructions if tools are not available - - Do not attempt to run assessment without MCP -- **For .NET**: Verify .NET SDK is installed - - Check with `dotnet --version` command - - Provide installation instructions if .NET SDK is missing -- **For JavaScript/TypeScript**: Verify Node.js and npm are installed - - Check with `npm --version` command - - Provide installation instructions if npm is missing - -**Assessment Failures**: -- Unsupported project type (only Java, .NET, and JavaScript/TypeScript supported) -- **For Java**: MCP server communication errors -- **For .NET**: - - dotnet-appcat tool installation failure - - dotnet-appcat command execution errors -- **For JavaScript/TypeScript**: - - npm-check-updates installation failure - - ncu command execution errors - - No package.json found at `{workspace-path}/package.json` -- Invalid project structure or build configuration - -**Report Generation Issues**: -- **For Java**: No report.json found under `{workspace-path}/.github/modernize/appcat/result/` or `{workspace-path}/.github/appmod/appcat/result/` subdirectories after MCP execution -- **For .NET**: Report not generated at `{workspace-path}/.github/modernize/appcat/result/report.json`, or `metadata.analysisStartTime` missing from report -- **For JavaScript/TypeScript**: Report not generated at `{workspace-path}/.github/modernize/assessment/js-assessment-report.md` -- Report file is corrupted or invalid JSON (Java/.NET only) - -For any failure, provide clear error messages and troubleshooting steps. diff --git a/.github/skills/business-workflows/SKILL.md b/.github/skills/business-workflows/SKILL.md deleted file mode 100644 index 73d79be9b..000000000 --- a/.github/skills/business-workflows/SKILL.md +++ /dev/null @@ -1,199 +0,0 @@ ---- -name: business-workflows -description: Generate core business workflow documentation with sequence diagram ---- - -# Core Business Workflows - -Analyze the project to document business processes end-to-end, domain entities, business rules, service-to-domain mapping, cross-service data flows, and decision logic. Generate a Mermaid sequence diagram showing the primary business workflow. Save to `.github/modernize/assessment/engines/business-workflows.md`. - -## Input Parameters - -- `workspace-path` (optional): Path to the project to analyze (defaults to current directory) - -## Scope Boundaries — Avoid Redundancy with Other Skills - -This skill is part of a set of four complementary assessment skills. To avoid content duplication across their output documents, observe these scope rules: - -- **Introduction**: Write a 1-2 sentence intro focused on the business domain (what the application does for its users). Do NOT restate the technology stack, database options, or framework versions. -- **Domain Entities table**: Focus on business meaning — entity description, bounded context, and business relationships. Do NOT reproduce entity field lists, data types, PK/FK annotations, or ORM mapping details (cascade, fetch strategy) — those are owned by the `data-architecture` skill. -- **Validation rules in workflow steps**: When describing a workflow step that involves validation, reference the rule by name (e.g., "PetValidator checks name and birthDate") rather than re-listing every constraint. Enumerate the full validation rules only once in the "Business Rules & Decision Logic" section. -- **Caching behavior**: If caching affects a workflow (e.g., vet list served from cache), mention the business impact (e.g., "vet data served from cache, reducing load") but do NOT describe the cache provider, TTL, configuration class, or JMX statistics — those are owned by the `data-architecture` skill. -- **API endpoint paths and HTTP methods**: Only mention endpoint paths as entry points for workflows (e.g., "Staff submits POST /owners/new"). Do NOT create endpoint inventory tables — those are owned by the `api-service-contracts` skill. - -## Execution Steps - -### Step 1: Generate Domain Entities Section - -Identify the domain model and produce the complete `## Domain Entities` section: - -- Identify domain entities and aggregates (DDD patterns if present) -- Focus on business meaning — entity description, bounded context, and business relationships -- Do NOT reproduce entity field lists, data types, PK/FK annotations, or ORM mapping details (cascade, fetch strategy) — those are owned by the `data-architecture` skill - -### Step 2: Generate Service-to-Domain Mapping Section - -Map each service to its bounded context and owned entities, then produce the complete `## Service-to-Domain Mapping` section (applies to microservice or multi-module architectures): - -- Service name → bounded context (e.g., `customers-service` → Customer Management, `visits-service` → Appointment Management) -- Domain entities owned by each service/context -- Cross-context data exchange patterns: how domains communicate (REST API, events, shared database) -- Data that spans contexts (e.g., `petId` as a foreign key in visits-service referencing customers-service's Pet entity) -- Aggregation boundaries: which service is the source of truth for which data - -### Step 3: Generate Primary Workflows Section - -Scan for business process entry points, trace each significant workflow end-to-end, and produce the complete `## Primary Workflows` section: - -**Entry points to scan:** -- Controllers/endpoints that initiate business processes (not just CRUD — look for multi-step operations) -- **API Gateway aggregation endpoints** that compose responses from multiple backend services — these are business workflow entry points even though they live in the gateway layer (e.g., fetching owner details combined with visit history) -- Scheduled tasks (`@Scheduled`, Quartz, Hangfire, cron jobs, `BackgroundService`) -- Event listeners (`@EventListener`, `@KafkaListener`, `INotificationHandler`, message handlers) -- CLI commands or batch job entry points -- Startup/initialization routines that set up business state - -**For each significant entry point, trace the flow:** -- Entry point → service layer → domain logic → persistence -- The sequence of operations: validation → business rule check → state mutation → side effects -- Branching logic (if/else, switch, strategy pattern) that represents business decisions -- Orchestration vs choreography patterns in multi-service workflows - -### Step 4: Generate Cross-Service Data Flows Section - -Trace cross-service data composition flows end-to-end and produce the complete `## Cross-Service Data Flows` section: - -- Gateway aggregation patterns: e.g., gateway fetches owner from customers-service → extracts pet IDs → fetches visits from visits-service → merges visits into pet records → returns composite response -- Which service provides which data and how they are joined/merged -- Circuit breaker fallback behavior that affects business outcomes (e.g., "when visits-service is unavailable, owner details are returned without visit history" — this is a business-relevant degradation, not just a technical detail) - -### Step 5: Generate Business Workflow Sequence Section - -Create a **Mermaid `sequenceDiagram`** showing the primary business workflow end-to-end and produce the complete `## Business Workflow Sequence` section: - -- Show the most important business process (e.g., "customer places order", "owner registers pet and schedules visit", "gateway aggregates owner with visit history") -- Include actors, services, and domain entities as participants -- Show business rule checks and decision points -- Annotate with business-relevant labels (not technical method names) -- Use `alt`/`else` blocks to show circuit breaker fallback paths that affect business outcomes -- Show cross-service data aggregation flows - -Example: - -~~~mermaid -sequenceDiagram - participant Owner - participant Gateway as "API Gateway" - participant CustSvc as "Customer Service" - participant VisitSvc as "Visit Service" - participant DB as "Database" - - Owner->>Gateway: View my pets and visits - Gateway->>CustSvc: Get owner details - CustSvc->>DB: Find owner with pets - DB-->>CustSvc: Owner + Pet list - CustSvc-->>Gateway: OwnerDetails(pets) - - Gateway->>Gateway: Extract pet IDs from response - Gateway->>VisitSvc: Get visits for pets (batch) - alt Visit Service Available - VisitSvc->>DB: Find visits by pet IDs - DB-->>VisitSvc: Visit records - VisitSvc-->>Gateway: Visits per pet - Gateway->>Gateway: Merge visits into pet records - else Visit Service Unavailable (Circuit Breaker) - Note over Gateway: Fallback - return owner without visits - end - Gateway-->>Owner: Complete owner profile with visits -~~~ - -### Step 6: Generate Business Rules & Decision Logic Section - -Extract and document business rules and cross-cutting concerns, and produce the complete `## Business Rules & Decision Logic` section: - -**Business Rules:** -- **Validation rules**: Input validation, field constraints, format checks, custom validators -- **Decision logic**: Conditional business logic, pricing rules, eligibility checks, approval workflows -- **State transitions**: Entity lifecycle states (e.g., Order: Created → Confirmed → Shipped → Delivered), state machines -- **Business constraints**: Uniqueness rules, capacity limits, temporal constraints (booking windows, cooldown periods) -- **Computed values**: Derived fields, calculated totals, aggregated metrics -- **Data integrity rules**: Bidirectional relationship maintenance (e.g., `owner.addPet(pet)` ensuring both sides of the relationship are set) - -**Cross-Cutting Concerns:** -- **Transactions**: Transaction boundaries, `@Transactional` scope, saga patterns, eventual consistency -- **Error handling**: Business exception types, compensating actions, dead-letter handling -- **Audit/logging**: Business event logging, audit trails, change tracking -- **Authorization**: Business-level authorization rules (role-based, attribute-based, resource ownership) - -### Step 7: Save Output - -Save to `.github/modernize/assessment/engines/business-workflows.md` with this exact structure: - -``` -# Core Business Workflows - -A brief introduction (1-2 sentences) summarizing the application's business domain. - -## Domain Entities - -[Table: Entity | Service / Bounded Context | Description | Key Relationships] - -## Service-to-Domain Mapping - -[Table: Service | Domain Context | Owned Entities | External Dependencies] - -## Primary Workflows - -### Workflow 1: [Name] - -[Description, steps, business rules involved, cross-service interactions] - -### Workflow 2: [Name] - -[Description, steps, business rules involved] - -## Cross-Service Data Flows - -[Description of aggregation/composition patterns, which service provides which data, how data is joined, fallback behavior when services are unavailable] - -## Business Workflow Sequence - -< Mermaid sequenceDiagram here, with alt/else blocks for fallback paths > - -## Business Rules & Decision Logic - -[Summary of key business rules, validation rules, state transitions, and decision points] -``` - -## Scaling Rules - -- If the project has **more than 10 distinct workflows**, focus on the 3-5 most important business processes and summarize the rest in a "Other Workflows" section -- Keep the sequence diagram under **40 participants and messages** to ensure readability and GitHub rendering compatibility -- For multi-module projects, focus on the primary end-to-end business workflow that spans modules -- Aggregate minor CRUD operations and show only workflows that involve business logic beyond simple create/read/update/delete - -## Mermaid Syntax Rules - -- Use `sequenceDiagram` -- Avoid special characters (`@`, `#`, `$`, `%`, `&`) in participant labels — use plain text or quoted labels -- Use `->>` for synchronous calls and `-->>` for responses -- Use `participant` with alias syntax for readable labels: `participant Svc as "OrderService"` -- Use `Note over` for annotations about business decisions or fallback behavior -- Use `alt`/`else`/`end` blocks for decision points and circuit breaker fallbacks -- Do not use backticks inside participant labels - -## Error Handling - -- **Unsupported project type**: Output a single line: `> ERROR: Unsupported project type. This skill supports Java, .NET, JavaScript, and TypeScript projects only.` -- **No business logic found**: Output: `> ERROR: No recognized business logic or workflows found at {workspace-path}. The project may be a library or framework without business processes.` -- **Insufficient info**: Generate a best-effort document from available data. Add a note: `> Note: Some workflows or business rules could not be fully traced.` - -## Success Criteria - -- Domain entities table lists key entities with their owning service/bounded context, descriptions, and relationships -- Service-to-domain mapping table maps each service to its domain context and owned entities -- At least one primary workflow is documented with steps, business rules, and cross-service interactions -- Cross-service data flows describe aggregation/composition patterns with fallback behavior -- Mermaid sequence diagram renders correctly showing end-to-end business workflow with `alt`/`else` blocks for fallbacks -- Business rules section summarizes validation, decision logic, state transitions, and constraints -- File saved to `.github/modernize/assessment/engines/business-workflows.md` diff --git a/.github/skills/configuration-inventory/SKILL.md b/.github/skills/configuration-inventory/SKILL.md deleted file mode 100644 index 780125774..000000000 --- a/.github/skills/configuration-inventory/SKILL.md +++ /dev/null @@ -1,205 +0,0 @@ ---- -name: configuration-inventory -description: Generate comprehensive configuration and externalized settings inventory ---- - -# Configuration & Externalized Settings Inventory - -Analyze the project to produce a comprehensive inventory of all configuration sources, build profiles, runtime profiles, externalized properties, secrets workflows, feature flags, startup dependencies, and framework versions. Save to `.github/modernize/assessment/engines/configuration-inventory.md`. - -> Note: This skill produces a comprehensive reference document. For structured findings suitable for automated processing, see `fact-profile-settings`, `fact-environment-variables`, and `fact-xml-configs`. - -## Input Parameters - -- `workspace-path` (optional): Path to the project to analyze (defaults to current directory) - -## Scope Boundaries — Avoid Redundancy with Other Skills - -This skill is part of a set of four complementary assessment skills. To avoid content duplication across their output documents, observe these scope rules: - -- **Introduction**: Write a 1-2 sentence intro focused on the configuration landscape (number of config sources, profiles, secrets approach). Do NOT restate the application's architecture type, business domain, or API surface. -- **Database architecture details** (entity models, ER diagrams, ORM mappings, caching strategy rationale, repository methods) are owned by the `data-architecture` skill. In the Properties Inventory, list database-related property keys and values as raw configuration entries, but do NOT explain their behavioral implications (e.g., do not explain what `spring.jpa.open-in-view=false` means for lazy loading — that belongs in `data-architecture.md`). -- **API endpoints** are owned by the `api-service-contracts` skill. Do NOT list HTTP endpoints, controller routes, or actuator paths. -- **Business workflows and validation rules** are owned by the `business-workflows` skill. Do NOT describe business processes or entity validation constraints. -- **Entity/domain model listings** are owned by `data-architecture` and `business-workflows`. Do NOT enumerate entity names, fields, or relationships. - -## Execution Steps - -### Step 1: Generate Configuration Sources Section - -Identify all configuration files and sources and produce the complete `## Configuration Sources` section: - -- Java (Spring): `application.properties`, `application.yml`, `bootstrap.properties`, `bootstrap.yml` — note that `bootstrap.*` files are distinct from `application.*` (bootstrap configures the config server connection and runs before application context; application configures the app itself) -- .NET: `appsettings.json`, `appsettings.{Environment}.json`, `web.config`, `launchSettings.json` -- JavaScript/TypeScript: `.env`, `.env.local`, `.env.production`, `config/*.js`, `config/*.ts` -- Shared: `docker-compose.yml` environment sections, Kubernetes ConfigMaps/Secrets YAML files -- Config server references: Spring Cloud Config (note the external Git repository URI), Azure App Configuration, AWS AppConfig, Consul KV -- Secret stores: HashiCorp Vault, Azure KeyVault, AWS Secrets Manager references -- External configuration repositories: document the URI/path of any external config repos (e.g., `spring.cloud.config.server.git.uri`) - -### Step 2: Generate Build Profiles Section - -Identify build-time profiles that affect compilation, packaging, and dependency resolution, and produce the complete `## Build Profiles` section: - -- **Java/Maven**: profiles in `pom.xml` (e.g., `springboot`, `buildDocker`, `dev`, `cloud`) — for each, document activation condition (auto, manual `-P`, system property `-Denv=`), purpose, and key dependencies or plugins added -- **Java/Gradle**: build types and flavors in `build.gradle` -- **.NET**: build configurations (Debug, Release), conditional compilation symbols, MSBuild properties -- **JavaScript/TypeScript**: build scripts in `package.json`, webpack/vite/esbuild configurations per environment - -For each build profile extract: -- Profile name -- Activation condition (automatic, manual flag, system property, environment variable) -- Purpose (what it enables) -- Key dependencies or plugins added/removed - -### Step 3: Generate Runtime Profiles Section - -List all runtime profile-specific or environment-specific configuration and produce the complete `## Runtime Profiles` section: - -- Java (Spring): Profile-specific files (`application-dev.yml`, `application-prod.yml`), `@Profile` annotations, `spring.profiles.active` settings, combined profile activation (e.g., `mysql,key-vault`) -- .NET: Environment-specific files (`appsettings.Development.json`, `appsettings.Production.json`), `ASPNETCORE_ENVIRONMENT` usage -- JavaScript/TypeScript: `.env.development`, `.env.production`, NODE_ENV-based branching -- Identify profile activation conditions, defaults, and how profiles compose (multiple active profiles) - -### Step 4: Generate Properties Inventory Section - -For each service/module, catalog all configuration properties and produce the complete `## Properties Inventory` section: - -- Property keys with their default values -- Which profiles/environments override each property -- Data types and expected value ranges (where inferable) -- Properties sourced from environment variables (`${ENV_VAR}`, `%ENV_VAR%`) -- Placeholder references and property resolution chain - -> Do NOT include JVM startup parameters, `-Xms`/`-Xmx` heap settings, `-D` system properties, container memory/CPU limits, or instance counts here — those belong in the `## Startup Parameters & Resource Requirements` section (Step 5). - -### Step 5: Generate Startup Parameters & Resource Requirements Section - -Document JVM startup options, runtime parameters, and per-service resource allocations, and produce the complete `## Startup Parameters & Resource Requirements` section: - -- JVM heap settings (`-Xms`, `-Xmx`) per service -- System properties passed at startup (`-Dspring.profiles.active=`, `-Dazure.keyvault.uri=`, etc.) -- Docker/container environment variable overrides (`SPRING_PROFILES_ACTIVE`, `ASPNETCORE_ENVIRONMENT`) -- Memory allocation per service (Docker `mem_limit`, Kubernetes `resources.requests/limits`, cloud deployment settings) -- CPU allocation if specified -- Instance count and scaling configuration -- JVM heap settings mapped to service memory allocation (e.g., `-Xms2048m -Xmx2048m` for 2Gi services) - -### Step 6: Generate Startup Dependency Chain Section - -Map the service startup order and readiness dependencies and produce the complete `## Startup Dependency Chain` section: - -- Which services must start before others (e.g., config-server → discovery-server → business services → gateway) -- Health-check/wait mechanisms: `dockerize` wait-for-TCP, Kubernetes readiness probes, Spring Cloud Config retry, Docker Compose `depends_on` with health checks -- Startup timeout configurations -- Service readiness indicators (actuator health endpoints, custom health checks) - -### Step 7: Generate Secrets & Sensitive Configuration Section - -Flag sensitive configuration entries and document the secrets provisioning workflow, and produce the complete `## Secrets & Sensitive Configuration` section (including the `### Secrets Provisioning Workflow` subsection): - -- Database passwords, API keys, connection strings with credentials -- Secret references: KeyVault URIs, Vault paths, encrypted property values -- Entries marked as sensitive by framework conventions (e.g., `spring.datasource.password`) -- **Do NOT output actual secret values** — show the reference path or "[MASKED]" placeholder -- Note encryption methods if present (Jasypt, DPAPI, sealed secrets) - -Document how secrets flow through the system (`### Secrets Provisioning Workflow`): -- Secret source: environment variables, Key Vault, Vault, AWS Secrets Manager, sealed secrets -- Identity/access model: managed identities, service principals, RBAC permissions (e.g., "system-assigned managed identity with `get` and `list` permissions on Key Vault") -- Provisioning sequence: how secrets are set up during deployment (e.g., GitHub Actions retrieves service principal credentials → authenticates → creates MySQL secrets → binds to services) -- Which services need which secrets (e.g., data services need MySQL connection strings, all services need config server credentials) - -### Step 8: Generate Feature Flags Section - -Identify feature toggles and conditional configuration and produce the complete `## Feature Flags` section: - -- Feature flag frameworks: Spring Feature Flags, LaunchDarkly, Unleash, .NET FeatureManagement, custom toggles -- Conditional beans/services (`@ConditionalOnProperty`, `@ConditionalOnExpression`) -- A/B testing flags and gradual rollout configurations -- Default values and controlling sources (config file, environment variable, remote service) - -### Step 9: Generate Framework & Runtime Versions Section - -Catalog the technology stack versions that affect configuration and produce the complete `## Framework & Runtime Versions` section: - -- Core framework versions: Spring Boot, Spring Cloud, ASP.NET Core, Node.js, Express -- Target language/runtime version: Java 8/11/17/21, .NET 6/7/8, Node.js 18/20 -- Key library versions: Hibernate, EF Core, Resilience4j, Eureka, etc. -- Docker base images and their versions (e.g., `openjdk:11-jre`, `mcr.microsoft.com/dotnet/aspnet:8.0`) -- Build tool versions: Maven, Gradle, MSBuild, npm/yarn/pnpm - -### Step 10: Save Output - -Save to `.github/modernize/assessment/engines/configuration-inventory.md` with this exact structure: - -``` -# Configuration & Externalized Settings Inventory - -A brief introduction (1-2 sentences) summarizing the configuration landscape. - -## Configuration Sources - -[Table: Source | Type | Path/Location | Notes] - -## Build Profiles - -[Table: Profile | Activation | Purpose | Key Dependencies/Plugins] - -## Runtime Profiles - -[Table: Profile | Activation Method | Config Files | Key Overrides] - -## Properties Inventory - -[Per-service tables: Property Key | Default | Profiles | Source] - -## Startup Parameters & Resource Requirements - -[Table: Service | JVM/Runtime Options | Memory | Instance Count] - -## Startup Dependency Chain - -[Ordered list: Service → waits for → Service, with mechanism (dockerize, health check, etc.)] - -## Secrets & Sensitive Configuration - -[Table: Secret Reference | Type | Storage (masked)] - -### Secrets Provisioning Workflow - -[Description of how secrets flow: source → identity/access → binding → services] - -## Feature Flags - -[Table: Flag Name | Default | Controlled By] - -## Framework & Runtime Versions - -[Table: Component | Version | Source] -``` - -## Scaling Rules - -- If the project has **more than 100 properties**, group by category (database, messaging, security, etc.) and show representative examples with counts -- For multi-module projects, organize the properties inventory by module/service -- Collapse repetitive property patterns (e.g., 20 similar cache TTL settings) into a summary row with count - -## Error Handling - -- **Unsupported project type**: Output a single line: `> ERROR: Unsupported project type. This skill supports Java, .NET, JavaScript, and TypeScript projects only.` -- **No configuration files found**: Output: `> ERROR: No recognized configuration files found at {workspace-path}. Verify the path is correct.` -- **Insufficient info**: Generate a best-effort inventory from available data. Add a note: `> Note: Some configuration sources or properties could not be fully identified.` - -## Success Criteria - -- Configuration sources table lists all discovered config files, external config repos, and secret stores -- Build profiles are documented separately from runtime profiles with activation conditions and purposes -- Runtime profiles are documented with config files and key overrides -- Properties inventory covers all discovered properties with defaults and sources -- Startup parameters and resource requirements are documented per service -- Startup dependency chain shows service boot order with wait mechanisms -- Secrets are identified with references (no actual values exposed) and the provisioning workflow is described -- Feature flags are cataloged with defaults and controlling sources -- Framework and runtime versions are documented -- File saved to `.github/modernize/assessment/engines/configuration-inventory.md` diff --git a/.github/skills/create-dotnet-upgrade-plan/SKILL.md b/.github/skills/create-dotnet-upgrade-plan/SKILL.md deleted file mode 100644 index bb6797d8e..000000000 --- a/.github/skills/create-dotnet-upgrade-plan/SKILL.md +++ /dev/null @@ -1,143 +0,0 @@ ---- -name: create-dotnet-upgrade-plan -description: Creates a .NET upgrade plan (plan.md and tasks.json) for a repository. Analyzes the project to determine if a .NET version upgrade is needed and generates a structured upgrade task. Use when the user wants to create, generate, or prepare an upgrade/migration plan for a .NET project or solution. ---- - -# Create .NET Upgrade Plan - -Generate an upgrade plan (`plan.md` and `.metadata/tasks.json`) for a .NET project or solution. This skill **only creates the plan** — it does not execute the upgrade. The upgrade execution is handled by the `modernize-dotnet-upgrade-engineer` agent during plan execution. - -> **Supported**: .NET upgrades only. Other languages are not supported. - ---- - -## CRITICAL: HEADLESS EXECUTION MODE - -**This skill operates in fully autonomous headless mode. NO USER INTERACTION IS PERMITTED.** - -### Non-Negotiable Rules - -1. **NEVER stop, pause, or wait for user input** — not for confirmations, selections, reviews, or approvals -2. **NEVER ask "Would you like to proceed?"** — just proceed -3. **NEVER ask "Should I continue?"** — just continue -4. **ALWAYS complete the entire workflow** — from start to finish without interruption - ---- - -## Input Parameters - -| Parameter | Required | Description | -|-----------|----------|-------------| -| `upgrade-prompt` | Yes | The user's upgrade request (e.g., ".NET 10", "net10.0", "latest LTS") | -| `modernization-work-folder` | Yes | The folder to save the upgrade plan outputs | - ---- - -## Input Validation - -**Valid requests**: .NET version upgrades, LTS migrations, .NET Framework to modern .NET migrations - -**Invalid requests**: Feature additions, bug fixes, refactoring, containerization, deployment, non-.NET languages - -If invalid, output exactly and STOP: -``` -ERROR: The provided prompt is not a valid upgrade request. Please specify a target version (e.g., '.NET 10', 'net10.0'). -``` - -If unsupported language, output exactly and STOP: -``` -ERROR: Only .NET upgrades are supported. The requested language is not supported. -``` - ---- - -## Guidelines - -Refer to `dotnet-upgrade-guideline.md` for the rules on when to create an upgrade task, target version selection, and framework compatibility. - -Key rules from the guideline: -- Only add an upgrade task if: the project is EOL, has Azure SDK incompatibility, or the user explicitly requests it -- Always upgrade to the **latest LTS** version unless the user specifies a different target -- Current latest LTS: **.NET 10** (`net10.0`) -- Create a **single** upgrade task that encompasses all necessary changes - ---- - -## Workflow - -### Step 1: Analyze the Project - -Examine the project to determine: -1. **Current .NET version**: Read `.csproj` files to find `` or `` -2. **Project type**: Is it .NET Framework (requires SDK-style conversion), .NET Core, or modern .NET? -3. **Solution structure**: Is it a single project or a multi-project solution? List all projects. - -### Step 2: Determine Upgrade Need - -Apply the rules from `dotnet-upgrade-guideline.md`: -- If the project's .NET version is EOL → upgrade needed -- If the project targets .NET Framework < 4.6.2 (Azure SDK incompatibility) → upgrade needed -- If the user explicitly requests an upgrade → upgrade needed -- Otherwise → no upgrade needed, output: `ERROR: No upgrade is needed. The project is already on a supported .NET version.` - -### Step 3: Generate plan.md - -Create `plan.md` in `${modernization-work-folder}/` with: -- Source .NET version (detected from the project) -- Target .NET version (from user request or latest LTS) -- List of projects in the solution -- High-level description of what the upgrade entails (e.g., SDK-style conversion, TFM update, NuGet updates, API migration) - -### Step 4: Generate tasks.json - -Generate `tasks.json` in `${modernization-work-folder}/.metadata/` following `tasks-schema.json` and `upgrade-plan-template.md`. - -Create a **single** upgrade task: - -**Task Generation Rules:** - -| Rule | Requirement | -|------|-------------| -| Task type | Use `upgrade` type | -| Task count | Exactly **one** upgrade task | -| Description | Include source and target .NET versions, project names | -| Reason | Include a **non-empty** `reason` field explaining why the upgrade is needed (for example: current .NET version is outdated or unsupported, target version standardization, required compatibility/security/support improvements) | -| Requirements | High-level summary of the upgrade scope (source version, target version, general areas affected). Do not include implementation details — the upgrade agent determines the specific steps. | -| Skills | Empty array `[]` — the upgrade agent handles execution internally | -| successCriteria values | Must be **strings** (`"true"`, not `true`) | -| status | Set to `"pending"` | -| id format | Use pattern `001-upgrade-dotnet-to-{target}` (e.g., `001-upgrade-dotnet-to-net10`) | - ---- - -## Success Criteria - -All of the following must be true: - -- [ ] `plan.md` exists in `${modernization-work-folder}/` -- [ ] `tasks.json` exists in `${modernization-work-folder}/.metadata/` -- [ ] `plan.md` clearly states source and target .NET versions -- [ ] `tasks.json` follows schema with all required fields -- [ ] Exactly one upgrade task in tasks.json -- [ ] Workflow completed without user interaction - ---- - -## Error Handling - -| Problem | Solution | -|---------|----------| -| No .csproj found | Output `ERROR: No supported .NET project files (.csproj) found in the repository.` | -| Cannot determine current .NET version | Output `ERROR: Could not determine the current .NET version from project files.` | -| Project already on target version | Output `ERROR: The project is already on the target .NET version.` | - ---- - -## Anti-Patterns (NEVER DO THESE) - -| Don't | Do Instead | -|-------|------------| -| Stop to ask user for confirmation | Accept defaults and continue | -| Create multiple granular upgrade tasks | Create a single upgrade task | -| Wait for user review | Complete workflow, then show final results | -| Ask "Should I proceed?" | Just proceed | diff --git a/.github/skills/create-dotnet-upgrade-plan/dotnet-upgrade-guideline.md b/.github/skills/create-dotnet-upgrade-plan/dotnet-upgrade-guideline.md deleted file mode 100644 index a5c97e03a..000000000 --- a/.github/skills/create-dotnet-upgrade-plan/dotnet-upgrade-guideline.md +++ /dev/null @@ -1,43 +0,0 @@ -# .NET Upgrade Task Guidelines - -Only add a .NET upgrade task if one of the following conditions is met: - -1. **End of Life (EOL)**: The project's .NET version is out of mainstream support. -2. **Azure SDK Compatibility**: The project targets a .NET Framework version older than 4.6.2, which does not support `netstandard2.0` and cannot use modern Azure SDK (`Azure.*`) packages. -3. **User Request**: The user explicitly requests a .NET version upgrade. - -The upgrade task must be the first task if it exists. - -### Target Version - -Always upgrade to the **latest LTS** version unless the user explicitly specifies a different target version. - -- Current latest LTS: **.NET 10** (`net10.0`) - -### Azure SDK Minimum .NET Version - -The modern Azure SDK for .NET (`Azure.*` packages) targets `netstandard2.0` as its baseline. The following .NET Framework versions do **not** support `netstandard2.0` and require an upgrade: - -| .NET Framework Version | `netstandard2.0` Support | Action | -|------------------------|:------------------------:|--------| -| 4.5 and below | ❌ | Upgrade required | -| 4.6 | ❌ | Upgrade required | -| 4.6.1 | ⚠️ Unreliable | Upgrade recommended | -| 4.6.2+ | ✅ | No upgrade needed for Azure SDK compatibility | - -> **Note**: .NET Framework 4.6.1 is technically listed as supporting `netstandard2.0` but has known issues. Microsoft recommends 4.7.2+ for reliable support. The Azure SDK explicitly targets `net462` as its minimum. - -## Framework Compatibility - -| Source Framework | Target Framework | SDK-Style Conversion Required | -|-----------------|:----------------:|:----------------------------:| -| .NET Framework 4.x | net10.0 | Yes | -| .NET Core 3.1 | net10.0 | No | -| .NET 5–9 | net10.0 | No | - -## .NET Task Selection Rules - -- **Rule 1 — Single task only**: Always create a **single** upgrade task that encompasses all necessary changes. The `modernize-dotnet-upgrade-engineer` agent handles the detailed breakdown during execution. -- **Rule 2 — EOL or Azure SDK incompatibility**: Create task "Upgrade .NET to latest LTS (net10.0)". Set the task `skills` array to `[]` (empty) — the upgrade agent handles execution internally. -- **Rule 3 — User-specified version**: Create task "Upgrade .NET to version X". Set the task `skills` array to `[]` (empty) — the upgrade agent handles execution internally. -- **Rule 4 — No upgrade needed**: If the project's .NET version is in support **and** the project targets .NET Framework 4.6.2+ (or any .NET Core / modern .NET) **and** the user did not request an upgrade, do **not** add an upgrade task. \ No newline at end of file diff --git a/.github/skills/create-dotnet-upgrade-plan/tasks-schema.json b/.github/skills/create-dotnet-upgrade-plan/tasks-schema.json deleted file mode 100644 index 397678e03..000000000 --- a/.github/skills/create-dotnet-upgrade-plan/tasks-schema.json +++ /dev/null @@ -1,349 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Modernization Tasks Template", - "description": "Schema for tasks-template.json. Preserve field descriptions because they are used to generate the JSON by LLM.", - "type": "object", - "required": ["$schema", "description", "tasks", "metadata"], - "additionalProperties": false, - "properties": { - "$schema": { - "type": "string", - "description": "JSON Schema reference used by the template." - }, - "description": { - "type": "string", - "description": "Tasks template for modernization plan. Generate this file alongside plan.md to track individual migration tasks." - }, - "tasks": { - "type": "array", - "description": "List of individual migration tasks.", - "items": { - "oneOf": [ - { "$ref": "#/$defs/transformTask" }, - { "$ref": "#/$defs/upgradeTask" }, - { "$ref": "#/$defs/integrationTestTask" }, - { "$ref": "#/$defs/containerizationTask" }, - { "$ref": "#/$defs/deploymentTask" }, - { "$ref": "#/$defs/securityTask" }, - { "$ref": "#/$defs/infrastructureTask" } - ] - } - }, - "metadata": { - "type": "object", - "description": "Metadata for the plan.", - "additionalProperties": false, - "required": ["planName", "projectName", "language", "createdAt", "version"], - "properties": { - "planName": { - "type": "string", - "description": "[Plan name]" - }, - "projectName": { - "type": "string", - "description": "[Application Name]" - }, - "language": { - "type": "string", - "description": "[Programming language]" - }, - "createdAt": { - "type": "string", - "description": "[ISO 8601 timestamp]", - "format": "date-time" - }, - "version": { - "type": "string", - "description": "Version of the template schema.", - "const": "1.0" - } - } - } - }, - "$defs": { - "taskBase": { - "type": "object", - "additionalProperties": false, - "required": ["type", "id", "description", "requirements"], - "properties": { - "type": { - "type": "string", - "description": "Task type identifier." - }, - "id": { - "type": "string", - "description": "[Unique identifier for this task, start with sequence and category name, like '001-transform-migration-rabbitmq-to-servicebus']" - }, - "description": { - "type": "string", - "description": "[Brief description of what this task achieves from the user's perspective. It should describe high level requirements without dictating implementation details like API calls, package names, or code structure]" - }, - "reason": { - "type": "string", - "description": "[Explain why this task is needed, e.g., the motivation, business justification, or technical necessity that drives this task]" - }, - "requirements": { - "type": "string", - "description": "[The specific requirements for this task including: what to migrate (features, APIs, patterns), constraints (backward compatibility, modules to avoid), target Azure services, and technical preferences (authentication methods, patterns). It should describe high level requirements without dictating implementation details like API calls, package names, or code structure]" - }, - "environmentConfiguration": { - "type": ["string"], - "description": "[Environment configuration from user input (e.g. endpoint, access id). Omit this field if not specified]" - }, - "status": { - "type": ["string"], - "description": "Task execution status. Only has value after task is executed.", - "enum": ["pending", "started", "success", "failed", "skipped"] - }, - "taskSummary": { - "type": "string", - "description": "Summary of task execution result. Only has value after task is executed." - }, - "dependencies": { - "type": "array", - "description": "[List of task IDs that this task depends on. The task will only be executed after all its dependencies have completed successfully.]", - "items": { - "type": "string" - } - } - } - }, - "skill": { - "type": "object", - "additionalProperties": false, - "required": ["name", "location"], - "properties": { - "name": { - "type": "string", - "description": "[The skill name that will be used for this task, e.g., 'migration-rabbitmq-to-servicebus'.]" - }, - "location": { - "type": "string", - "description": "Skill location: project, remote and builtin, builtin is renamed from custom", - "enum": ["project","remote","builtin"] - } - } - }, - "successCriteria": { - "type": "object", - "additionalProperties": false, - "required": [ - "passBuild", - "generateNewUnitTests", - "passUnitTests" - ], - "description": "The task success criteria to validate after task execution.", - "properties": { - "passBuild": { - "type": ["string"], - "default": "true", - "description": "Project must compile successfully after migration, use default value if user does not specify" - }, - "generateNewUnitTests": { - "type": ["string"], - "default": "false", - "description": "Create mock-based unit tests for newly added Azure integration code to ensure test coverage, use default value if user does not specify" - }, - "passUnitTests": { - "type": ["string"], - "default": "true", - "description": "All unit tests must pass; mock dependent Azure resources if not provided, use default value if user does not specify" - } - } - }, - "successCriteriaStatus": { - "type": "object", - "not": { - "type": "null" - }, - "additionalProperties": false, - "description": "Optional validation status for each success criterion. Omit this field until validation is complete. If present, use true/false string values.", - "properties": { - "passBuild": { - "type": ["string"], - "description": "Validation status of passBuild criterion. true means passed, false means failed." - }, - "generateNewUnitTests": { - "type": ["string"], - "description": "Validation status of generateNewUnitTests criterion. true means passed, false means failed." - }, - "passUnitTests": { - "type": ["string"], - "description": "Validation status of passUnitTests criterion. true means passed, false means failed." - } - } - }, - "transformTask": { - "allOf": [ - { "$ref": "#/$defs/taskBase" }, - { - "type": "object", - "additionalProperties": false, - "required": ["type", "successCriteria"], - "properties": { - "type": { - "const": "transform", - "description": "transform task template" - }, - "skills": { - "type": "array", - "description": "The skills that will be used for this task, it is started with migration and looks like 'migration-rabbitmq-to-servicebus-mi'", - "items": { "$ref": "#/$defs/skill" } - }, - "successCriteria": { "$ref": "#/$defs/successCriteria" }, - "successCriteriaStatus": { "$ref": "#/$defs/successCriteriaStatus" } - } - } - ] - }, - "upgradeTask": { - "allOf": [ - { "$ref": "#/$defs/taskBase" }, - { - "type": "object", - "additionalProperties": false, - "required": ["type", "successCriteria"], - "properties": { - "type": { - "const": "upgrade", - "description": "Upgrade task template" - }, - "successCriteria": { "$ref": "#/$defs/successCriteria" }, - "successCriteriaStatus": { "$ref": "#/$defs/successCriteriaStatus" } - } - } - ] - }, - "containerizationTask": { - "allOf": [ - { "$ref": "#/$defs/taskBase" }, - { - "type": "object", - "additionalProperties": false, - "required": ["type", "dockerfilePath"], - "properties": { - "type": { - "const": "containerization", - "description": "Containerization task template - Only include if the target deployment requires containerization (e.g., AKS, ACA) or if the user explicitly requested containerization. Skip for non-containerized deployments." - }, - "dockerfilePath": { - "type": "string", - "description": "[Path to Dockerfile, indicate if existing or to be created]" - } - } - } - ] - }, - "deploymentTask": { - "allOf": [ - { "$ref": "#/$defs/taskBase" }, - { - "type": "object", - "additionalProperties": false, - "required": ["type", "targetAzureService", "resourceStatus", "deploymentTool", "skills"], - "properties": { - "type": { - "const": "deployment", - "description": "Deployment task template - Containerize, provision and deploy the application to Azure. Only include if the user explicitly requested deployment." - }, - "skills": { - "type": "array", - "description": "The deployment skill to use based on targetAzureService: 'azcli-aks-deploy' for Azure Kubernetes Service, 'azcli-containerapp-deploy' for Azure Container Apps, 'azcli-appservice-deploy' for Azure App Service, 'azcli-functionapp-deploy' for Azure Function App, 'azcli-appservicemi-deploy' for Azure App Service with Managed Identity", - "items": { "$ref": "#/$defs/skill" } - }, - "targetAzureService": { - "type": "string", - "description": "[Azure service name, e.g., Azure Container Apps, AKS, App Service]" - }, - "resourceStatus": { - "type": "string", - "description": "[Specify if using existing resource or will create new service]" - }, - "deploymentTool": { - "type": "string", - "description": "Infrastructure as Code tool type. Default to 'terraform' for aks, 'bicep' for other services if not specified.", - "enum": ["bicep", "terraform"] - } - } - } - ] - }, - "integrationTestTask": { - "allOf": [ - { "$ref": "#/$defs/taskBase" }, - { - "type": "object", - "additionalProperties": false, - "required": ["type", "layers"], - "properties": { - "type": { - "const": "integrationTest", - "description": "Integration test task template - Generate and run integration tests for migrated Azure services. Only include when user explicitly requests integration testing. This task runs after all transform/upgrade tasks but before containerization." - }, - "layers": { - "type": "array", - "description": "[Array of test layers to execute, e.g., [1, 2] for Layer 1 (Local Integration with TestContainers) and Layer 2 (Smoke Tests)]", - "items": { - "type": "number", - "enum": [1, 2] - }, - "minItems": 1 - } - } - } - ] - }, - "securityTask": { - "allOf": [ - { "$ref": "#/$defs/taskBase" }, - { - "type": "object", - "additionalProperties": false, - "required": ["type", "successCriteria"], - "properties": { - "type": { - "const": "security", - "description": "Security task template - Include when security remediation, vulnerability scanning, or compliance checks are required." - }, - "cveReport": { - "type": ["string"], - "description": "Path to final-cve-report.json. Only has value after CVE checking is complete." - }, - "successCriteria": { "$ref": "#/$defs/successCriteria" }, - "successCriteriaStatus": { "$ref": "#/$defs/successCriteriaStatus" } - } - } - ] - }, - "infrastructureTask": { - "allOf": [ - { "$ref": "#/$defs/taskBase" }, - { - "type": "object", - "additionalProperties": false, - "required": ["type", "skills", "iacType", "provision"], - "properties": { - "type": { - "const": "infrastructure", - "description": "Infrastructure task template - Generate IaC files (Bicep or Terraform) to provision Azure resources." - }, - "iacType": { - "type": "string", - "description": "Infrastructure as Code tool type.", - "enum": ["bicep", "terraform"] - }, - "provision": { - "type": "boolean", - "description": "Whether to provision Azure resources after generating IaC files." - }, - "skills": { - "type": "array", - "description": "The skill used for this task, e.g., 'infrastructure-bicep-generation' or 'infrastructure-terraform-generation'", - "items": { "$ref": "#/$defs/skill" } - } - } - } - ] - } - } -} diff --git a/.github/skills/create-dotnet-upgrade-plan/upgrade-plan-template.md b/.github/skills/create-dotnet-upgrade-plan/upgrade-plan-template.md deleted file mode 100644 index b534fece6..000000000 --- a/.github/skills/create-dotnet-upgrade-plan/upgrade-plan-template.md +++ /dev/null @@ -1,42 +0,0 @@ -# .NET Upgrade Plan Template - -## Schema Rules - -- Use `upgradeTask` type from `tasks-schema.json` -- `successCriteria` values: **strings** (`"true"`, `"false"`) -- `skills.location`: `"builtin"` | `"project"` | `"remote"` -- `status`: `"pending"` - -## Example: tasks.json - -```json -{ - "$schema": "tasks-schema.json", - "description": ".NET version upgrade plan from .NET Framework 4.6.1 to .NET 10.0", - "tasks": [ - { - "type": "upgrade", - "id": "001-upgrade-dotnet-to-net10", - "description": "Upgrade ContosoUniversity from .NET Framework 4.6.1 to .NET 10.0", - "reason": ".NET Framework 4.6.1 reached end of support on April 26, 2022 and does not meet the minimum .NET Framework 4.6.2 requirement for Azure SDK (.NET Standard 2.0) compatibility. Upgrading to .NET 10.0 LTS ensures long-term support, access to modern APIs, and full Azure SDK compatibility.", - "requirements": "Upgrade the project from .NET Framework 4.6.1 to .NET 10.0, including project file modernization, dependency updates, and API compatibility fixes.", - "environmentConfiguration": null, - "skills": [], - "successCriteria": { - "passBuild": "true", - "generateNewUnitTests": "false", - "passUnitTests": "true", - "securityComplianceCheck": "true" - }, - "status": "pending" - } - ], - "metadata": { - "planName": "upgrade-to-lts", - "projectName": "ContosoUniversity", - "language": "dotnet", - "createdAt": "2026-02-13T00:00:00.000Z", - "version": "1.0" - } -} -``` diff --git a/.github/skills/create-java-upgrade-plan/SKILL.md b/.github/skills/create-java-upgrade-plan/SKILL.md deleted file mode 100644 index fe4b54afb..000000000 --- a/.github/skills/create-java-upgrade-plan/SKILL.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -name: create-java-upgrade-plan -description: Create an upgrade plan to migrate a Java project to latest LTS versions ---- - -# Create Upgrade Plan - -Creates `tasks.json` (in `.metadata/` subdirectory) and `plan.md` for upgrading Java projects to target versions. - -> **Supported**: Java upgrades only. .NET and other languages are not supported. - -## ⚠️ CRITICAL: Do NOT Read Files - -**Do NOT read any workspace files** (pom.xml, build.gradle, source code, etc.). - -This plan may apply to multiple repositories not yet cloned. Generate a **generic plan** based solely on the user's prompt—never detect or mention "current versions." - -## User Input - -upgrade-prompt (Mandatory): The user's upgrade request (e.g., "Java 17", "Spring Boot 3.2", or "Upgrade to the latest LTS versions") -modernization-work-folder (Mandatory): The folder to save the upgrade plan - -## Validation - -**Valid requests**: Java/framework version upgrades, LTS migrations, dependency bumps -**Invalid requests**: Feature additions, bug fixes, refactoring, containerization, deployment, non-Java languages (.NET, Python, etc.) - -If invalid, output exactly: -``` -ERROR: The provided prompt is not a valid upgrade request. Please specify a target version (e.g., 'Java 17', 'Spring Boot 3.2'). -``` - -If unsupported language, output exactly: -``` -ERROR: Only Java upgrades are supported. The requested language is not supported. -``` -Then STOP—do not create files or ask for clarification. - -## Workflow - -1. **Parse request** → Extract target versions (defaults: Java 25, Spring Boot 4.x) -2. **Generate tasks.json** → Follow `tasks-schema.json` and `upgrade-plan-template.md`, and save it to `.metadata/tasks.json` -3. **Generate plan.md** → Brief overview of the upgrade plan - -## Task Rules - -- **Java Upgrade Task Guidelines**: You must refer to the ./java-upgrade-guideline.md for specific rules and guidelines when creating Java upgrade tasks. -- Create **only** `upgrade` type tasks -- Specify **target versions only**—never "from version X" -- Use `builtin` skills (e.g., `java-version-upgrade`) -- `successCriteria` values must be **strings** (`"true"`, not `true`) -- Set `status` to `"pending"` - -## Output - -Save to `${modernization-work-folder}/`: -- `.metadata/tasks.json` — Upgrade tasks per schema -- `plan.md` — Plan overview diff --git a/.github/skills/create-java-upgrade-plan/java-upgrade-guideline.md b/.github/skills/create-java-upgrade-plan/java-upgrade-guideline.md deleted file mode 100644 index 270412a96..000000000 --- a/.github/skills/create-java-upgrade-plan/java-upgrade-guideline.md +++ /dev/null @@ -1,44 +0,0 @@ -# Java Upgrade Task Guidelines - -Only add an upgrade task if the user explicitly requests it. The upgrade task must be the first task if it exists. - -## Latest Stable Versions - -- Java: 25 -- Spring Boot: 4.x -- Spring Framework: 7.x - -## Supported Upgrade Versions - -- Java: 11, 17, 21, 25 -- Spring Boot: 3.x, 4.x -- Spring Framework: 6.x, 7.x - -## Framework Compatibility - -| Spring Boot | Spring Framework | Jakarta EE | Minimum Java | Maximum Java | -|-------------|:----------------:|:----------:|:------------:|:------------:| -| 2.x | 5.x | JavaEE (javax.*) | 8 | 11 | -| 3.x | 6.x | Jakarta EE (jakarta.*) | 17 | 21 | -| 4.x | 7.x | Jakarta EE (jakarta.*) | 25 | 25 | - -## Upgrade Task Types and Included Changes - -| Task Type | Spring Framework Upgrade | Jakarta EE Migration (javax.* → jakarta.*) | JDK/Java | -|-----------|:------------------------:|:------------------------------------------:|:-----------:| -| Spring Boot 4.x upgrade | 7.x | ✓ | 25 | -| Spring Boot 3.x upgrade | 6.x | ✓ | 21 | -| Spring Framework 7.x upgrade | — | ✓ | 25 | -| Spring Framework 6.x upgrade | — | ✓ | 21 | -| Jakarta EE upgrade | — | ✓ | 21 | -| JDK/Java upgrade | — | — | to specified version | - -## Java Task Selection Rules - -When selecting the Java upgrade task type, follow these rules in order: - -- **Rule 1 — No redundant sub-tasks**: Each upgrade type (Spring Boot, Spring Framework, Jakarta EE, JDK/Java) is hierarchical — higher-level tasks already include lower-level ones. Never create a lower-level task that is already covered by a selected higher-level task. For example, if a Spring Boot 4.x upgrade task is selected (which already includes JDK 25), do NOT also create a separate JDK/Java upgrade task. -- **Rule 2 — User-specified framework request doesn't fully match system state**: When the user requests a **framework** upgrade (e.g., Spring Boot, Spring Framework) but the target version they specify is not the latest available, select the highest-level task applicable and prompt the user to clarify. For example, if the user asks to "upgrade Spring Boot to 3.x" but Spring Boot 4.x is available, create a Spring Boot 3.x upgrade task and add a clarification question asking whether they want 4.x. **This rule does NOT apply to pure JDK/Java upgrade requests — see Rule 4.** -- **Rule 3 — User-specified request matches system state**: Select the most closely matching task type that directly matches the user's request and fits the system. For example, if the user asks to "upgrade JDK" and the JDK is outdated, create a JDK/Java upgrade task — NOT a higher-level Spring Boot or Spring Framework upgrade task. -- **Rule 4 — Never upgrade other frameworks on a JDK/Java request**: When the user explicitly requests a JDK/Java upgrade, you MUST NOT upgrade Spring Boot, Spring Framework, or Jakarta EE — create only a JDK/Java upgrade task targeting the user-specified version. If the project's existing Spring Boot, Spring Framework, or Jakarta EE versions are incompatible with the target Java version (per the Framework Compatibility table above), add a clarification question asking the user whether they also want to upgrade the incompatible framework(s) to a compatible version. -- **Rule 5 — Ask for clarification when the selected task diverges from the user's request**: Whenever the task you create differs from what the user explicitly asked for (e.g., upgrading to a different version, choosing a higher-level task type, or skipping a requested change due to compatibility), you MUST use the `ask_user` tool to explain what was selected, why it differs, and ask the user to confirm or adjust. If `ask_user` is not available, add the question to the "## Open Questions" section of `plan.md`. Never silently override the user's intent. \ No newline at end of file diff --git a/.github/skills/create-java-upgrade-plan/tasks-schema.json b/.github/skills/create-java-upgrade-plan/tasks-schema.json deleted file mode 100644 index 397678e03..000000000 --- a/.github/skills/create-java-upgrade-plan/tasks-schema.json +++ /dev/null @@ -1,349 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Modernization Tasks Template", - "description": "Schema for tasks-template.json. Preserve field descriptions because they are used to generate the JSON by LLM.", - "type": "object", - "required": ["$schema", "description", "tasks", "metadata"], - "additionalProperties": false, - "properties": { - "$schema": { - "type": "string", - "description": "JSON Schema reference used by the template." - }, - "description": { - "type": "string", - "description": "Tasks template for modernization plan. Generate this file alongside plan.md to track individual migration tasks." - }, - "tasks": { - "type": "array", - "description": "List of individual migration tasks.", - "items": { - "oneOf": [ - { "$ref": "#/$defs/transformTask" }, - { "$ref": "#/$defs/upgradeTask" }, - { "$ref": "#/$defs/integrationTestTask" }, - { "$ref": "#/$defs/containerizationTask" }, - { "$ref": "#/$defs/deploymentTask" }, - { "$ref": "#/$defs/securityTask" }, - { "$ref": "#/$defs/infrastructureTask" } - ] - } - }, - "metadata": { - "type": "object", - "description": "Metadata for the plan.", - "additionalProperties": false, - "required": ["planName", "projectName", "language", "createdAt", "version"], - "properties": { - "planName": { - "type": "string", - "description": "[Plan name]" - }, - "projectName": { - "type": "string", - "description": "[Application Name]" - }, - "language": { - "type": "string", - "description": "[Programming language]" - }, - "createdAt": { - "type": "string", - "description": "[ISO 8601 timestamp]", - "format": "date-time" - }, - "version": { - "type": "string", - "description": "Version of the template schema.", - "const": "1.0" - } - } - } - }, - "$defs": { - "taskBase": { - "type": "object", - "additionalProperties": false, - "required": ["type", "id", "description", "requirements"], - "properties": { - "type": { - "type": "string", - "description": "Task type identifier." - }, - "id": { - "type": "string", - "description": "[Unique identifier for this task, start with sequence and category name, like '001-transform-migration-rabbitmq-to-servicebus']" - }, - "description": { - "type": "string", - "description": "[Brief description of what this task achieves from the user's perspective. It should describe high level requirements without dictating implementation details like API calls, package names, or code structure]" - }, - "reason": { - "type": "string", - "description": "[Explain why this task is needed, e.g., the motivation, business justification, or technical necessity that drives this task]" - }, - "requirements": { - "type": "string", - "description": "[The specific requirements for this task including: what to migrate (features, APIs, patterns), constraints (backward compatibility, modules to avoid), target Azure services, and technical preferences (authentication methods, patterns). It should describe high level requirements without dictating implementation details like API calls, package names, or code structure]" - }, - "environmentConfiguration": { - "type": ["string"], - "description": "[Environment configuration from user input (e.g. endpoint, access id). Omit this field if not specified]" - }, - "status": { - "type": ["string"], - "description": "Task execution status. Only has value after task is executed.", - "enum": ["pending", "started", "success", "failed", "skipped"] - }, - "taskSummary": { - "type": "string", - "description": "Summary of task execution result. Only has value after task is executed." - }, - "dependencies": { - "type": "array", - "description": "[List of task IDs that this task depends on. The task will only be executed after all its dependencies have completed successfully.]", - "items": { - "type": "string" - } - } - } - }, - "skill": { - "type": "object", - "additionalProperties": false, - "required": ["name", "location"], - "properties": { - "name": { - "type": "string", - "description": "[The skill name that will be used for this task, e.g., 'migration-rabbitmq-to-servicebus'.]" - }, - "location": { - "type": "string", - "description": "Skill location: project, remote and builtin, builtin is renamed from custom", - "enum": ["project","remote","builtin"] - } - } - }, - "successCriteria": { - "type": "object", - "additionalProperties": false, - "required": [ - "passBuild", - "generateNewUnitTests", - "passUnitTests" - ], - "description": "The task success criteria to validate after task execution.", - "properties": { - "passBuild": { - "type": ["string"], - "default": "true", - "description": "Project must compile successfully after migration, use default value if user does not specify" - }, - "generateNewUnitTests": { - "type": ["string"], - "default": "false", - "description": "Create mock-based unit tests for newly added Azure integration code to ensure test coverage, use default value if user does not specify" - }, - "passUnitTests": { - "type": ["string"], - "default": "true", - "description": "All unit tests must pass; mock dependent Azure resources if not provided, use default value if user does not specify" - } - } - }, - "successCriteriaStatus": { - "type": "object", - "not": { - "type": "null" - }, - "additionalProperties": false, - "description": "Optional validation status for each success criterion. Omit this field until validation is complete. If present, use true/false string values.", - "properties": { - "passBuild": { - "type": ["string"], - "description": "Validation status of passBuild criterion. true means passed, false means failed." - }, - "generateNewUnitTests": { - "type": ["string"], - "description": "Validation status of generateNewUnitTests criterion. true means passed, false means failed." - }, - "passUnitTests": { - "type": ["string"], - "description": "Validation status of passUnitTests criterion. true means passed, false means failed." - } - } - }, - "transformTask": { - "allOf": [ - { "$ref": "#/$defs/taskBase" }, - { - "type": "object", - "additionalProperties": false, - "required": ["type", "successCriteria"], - "properties": { - "type": { - "const": "transform", - "description": "transform task template" - }, - "skills": { - "type": "array", - "description": "The skills that will be used for this task, it is started with migration and looks like 'migration-rabbitmq-to-servicebus-mi'", - "items": { "$ref": "#/$defs/skill" } - }, - "successCriteria": { "$ref": "#/$defs/successCriteria" }, - "successCriteriaStatus": { "$ref": "#/$defs/successCriteriaStatus" } - } - } - ] - }, - "upgradeTask": { - "allOf": [ - { "$ref": "#/$defs/taskBase" }, - { - "type": "object", - "additionalProperties": false, - "required": ["type", "successCriteria"], - "properties": { - "type": { - "const": "upgrade", - "description": "Upgrade task template" - }, - "successCriteria": { "$ref": "#/$defs/successCriteria" }, - "successCriteriaStatus": { "$ref": "#/$defs/successCriteriaStatus" } - } - } - ] - }, - "containerizationTask": { - "allOf": [ - { "$ref": "#/$defs/taskBase" }, - { - "type": "object", - "additionalProperties": false, - "required": ["type", "dockerfilePath"], - "properties": { - "type": { - "const": "containerization", - "description": "Containerization task template - Only include if the target deployment requires containerization (e.g., AKS, ACA) or if the user explicitly requested containerization. Skip for non-containerized deployments." - }, - "dockerfilePath": { - "type": "string", - "description": "[Path to Dockerfile, indicate if existing or to be created]" - } - } - } - ] - }, - "deploymentTask": { - "allOf": [ - { "$ref": "#/$defs/taskBase" }, - { - "type": "object", - "additionalProperties": false, - "required": ["type", "targetAzureService", "resourceStatus", "deploymentTool", "skills"], - "properties": { - "type": { - "const": "deployment", - "description": "Deployment task template - Containerize, provision and deploy the application to Azure. Only include if the user explicitly requested deployment." - }, - "skills": { - "type": "array", - "description": "The deployment skill to use based on targetAzureService: 'azcli-aks-deploy' for Azure Kubernetes Service, 'azcli-containerapp-deploy' for Azure Container Apps, 'azcli-appservice-deploy' for Azure App Service, 'azcli-functionapp-deploy' for Azure Function App, 'azcli-appservicemi-deploy' for Azure App Service with Managed Identity", - "items": { "$ref": "#/$defs/skill" } - }, - "targetAzureService": { - "type": "string", - "description": "[Azure service name, e.g., Azure Container Apps, AKS, App Service]" - }, - "resourceStatus": { - "type": "string", - "description": "[Specify if using existing resource or will create new service]" - }, - "deploymentTool": { - "type": "string", - "description": "Infrastructure as Code tool type. Default to 'terraform' for aks, 'bicep' for other services if not specified.", - "enum": ["bicep", "terraform"] - } - } - } - ] - }, - "integrationTestTask": { - "allOf": [ - { "$ref": "#/$defs/taskBase" }, - { - "type": "object", - "additionalProperties": false, - "required": ["type", "layers"], - "properties": { - "type": { - "const": "integrationTest", - "description": "Integration test task template - Generate and run integration tests for migrated Azure services. Only include when user explicitly requests integration testing. This task runs after all transform/upgrade tasks but before containerization." - }, - "layers": { - "type": "array", - "description": "[Array of test layers to execute, e.g., [1, 2] for Layer 1 (Local Integration with TestContainers) and Layer 2 (Smoke Tests)]", - "items": { - "type": "number", - "enum": [1, 2] - }, - "minItems": 1 - } - } - } - ] - }, - "securityTask": { - "allOf": [ - { "$ref": "#/$defs/taskBase" }, - { - "type": "object", - "additionalProperties": false, - "required": ["type", "successCriteria"], - "properties": { - "type": { - "const": "security", - "description": "Security task template - Include when security remediation, vulnerability scanning, or compliance checks are required." - }, - "cveReport": { - "type": ["string"], - "description": "Path to final-cve-report.json. Only has value after CVE checking is complete." - }, - "successCriteria": { "$ref": "#/$defs/successCriteria" }, - "successCriteriaStatus": { "$ref": "#/$defs/successCriteriaStatus" } - } - } - ] - }, - "infrastructureTask": { - "allOf": [ - { "$ref": "#/$defs/taskBase" }, - { - "type": "object", - "additionalProperties": false, - "required": ["type", "skills", "iacType", "provision"], - "properties": { - "type": { - "const": "infrastructure", - "description": "Infrastructure task template - Generate IaC files (Bicep or Terraform) to provision Azure resources." - }, - "iacType": { - "type": "string", - "description": "Infrastructure as Code tool type.", - "enum": ["bicep", "terraform"] - }, - "provision": { - "type": "boolean", - "description": "Whether to provision Azure resources after generating IaC files." - }, - "skills": { - "type": "array", - "description": "The skill used for this task, e.g., 'infrastructure-bicep-generation' or 'infrastructure-terraform-generation'", - "items": { "$ref": "#/$defs/skill" } - } - } - } - ] - } - } -} diff --git a/.github/skills/create-java-upgrade-plan/upgrade-plan-template.md b/.github/skills/create-java-upgrade-plan/upgrade-plan-template.md deleted file mode 100644 index 144ae61e4..000000000 --- a/.github/skills/create-java-upgrade-plan/upgrade-plan-template.md +++ /dev/null @@ -1,51 +0,0 @@ -# Upgrade Plan Template - -> **Important**: Specify only TARGET versions—never "current" or "from" versions. Do NOT read project files. - -## Schema Rules - -- Use `upgradeTask` type from `tasks-schema.json` -- `successCriteria` values: **strings** (`"true"`, `"false"`) -- `skills.location`: `"builtin"` | `"project"` | `"remote"` -- `status`: `"pending"` - -## Example: tasks.json -{ - "description": "Upgrade plan to migrate project to latest LTS versions", - "tasks": [ - { - "type": "upgrade", - "id": "001-upgrade-java-spring-boot", - "description": "Upgrade to Java 25 and Spring Boot 4.x", - "requirements": "Upgrade JDK to 25, Spring Boot to 4.x, Spring Framework to 7.x, and migrate javax.* to jakarta.* if needed", - "environmentConfiguration": null, - "skills": [{ "name": "java-version-upgrade", "location": "builtin" }], - "successCriteria": { - "passBuild": "true", - "generateNewUnitTests": "false", - "passUnitTests": "true" - }, - "status": "pending" - } - ], - "metadata": { - "planName": "upgrade-to-lts", - "projectName": "application", - "language": "java", - "createdAt": "2026-02-06T00:00:00.000Z", - "version": "1.0" - } -} -``` - -## Example: plan.md - -```markdown -# Upgrade Plan - -## Overview -Upgrade to latest LTS versions. - -## Tasks -See the .metadata/tasks.json for detailed task breakdown. -``` diff --git a/.github/skills/create-modernization-plan/SKILL.md b/.github/skills/create-modernization-plan/SKILL.md deleted file mode 100644 index 13ea71ceb..000000000 --- a/.github/skills/create-modernization-plan/SKILL.md +++ /dev/null @@ -1,129 +0,0 @@ ---- -name: create-modernization-plan -description: Create a modernization plan to migrate the project to Azure ---- - -# Create modernization plan - -This skill is used to create a modernization plan to migrate the a given project to Azure - -## User Input - -modernization-prompt: The user input to generate the modernization plan -modernization-work-folder (Mandatory): The folder to save the modernization plan -github-issue-link (Optional): A github issue to track the modernization status, to be filled into plan template -assessment-report (Optional): A assessment report for the project will be modernized, it will provide the data about the project for modernization -plan-name (Optional): The plan name to be filled into plan template -language (Mandatory): The programming language of the project (java or dotnet) - -## Supported Task Patterns - -Read the supported patterns file based on the language: -- For .NET projects: Read `supported-patterns-dotnet.md` -- For all other projects: Read `supported-patterns-java.md`. Default option. - -These files contain the list of supported task patterns with and without skill definitions. If a skill is available, the skill location should be set to `builtin`. - -## Workflow - -Given the user input, do this: - -1. Double Check the issues - **IMPORTANT**: - - If you are given an assessment-report, you need to double check if the issue really exist in current project. If not, please ignore this issue when you generate the plan - -2. **Load context**: Retrieve information for plan, you can read - 1) Analysis the supported patterns to find the right tasks for the issues - 2) Analysis modernization requirement from user input - -3. **Clarification and Questionnaire** (only when the `ask_user` tool is available): If there are any open issues or ambiguities that need user input, use the following steps to answer any questions. Also ask the questions outlined in `questionnaire.md` via `ask_user` tool to scope the modernization plan. For questionnaire questions, if the user input already provides the answer, skip asking that question and use the provided information as the answer. - 1) Use the `ask_user` tool to ask the user each clarification question directly. Wait for the user's response before proceeding. - 2) Record each question and answer for use in the summary step. - 3) If the `ask_user` tool is not available, skip this step entirely and proceed to plan generation using best-effort defaults. - -4. **Summary & Confirmation** (only when the `ask_user` tool is available): - 1) Present a summary to the user via `ask_user` that includes: - - All clarification questions and the user's answers (if any were asked in step 3) - - The planned task list with key details for each task: task name, type, matched skill/pattern, and a brief description of what it will do - 2) Ask the user to confirm the summary is correct, or provide additional input to adjust any answers or task list. - 3) If the user provides additional input, incorporate the changes. If the user chooses to skip or confirms, proceed to plan generation. - 4) If the `ask_user` tool is not available, skip this step entirely. - -5. **Generate plan and tasks**: Generate plan.md and tasks.json using the appropriate templates: - - **Template Selection**: - - Use **plan-template.md** for code migration, containerization, and deployment tasks - - Use **security-plan-template.md** to include a security/CVE remediation task in every modernization plan. - - Use **infra-plan-template.md** ONLY when user explicitly requests infrastructure (e.g., "prepare infrastructure", "create landing zone", "provision resources", "generate Bicep/Terraform") - - **Plan Generation**: - 1) Follow the structure of the selected template to generate the plan - 2) Follow the rules defined in the template to fill in the sections with relevant information based on the analysis of user input and content of mentioned files - 3) Save the plan in folder ${modernization-work-folder} with the filename plan.md. If a plan already exists, overwrite it. - 4) Generate a separate tasks.json file following the tasks-schema.json schema with all upgrade, transform, security, integration test, infrastructure, containerization, and deployment tasks - 5) Save the tasks in folder ${modernization-work-folder}/.metadata/ with the filename tasks.json. If tasks.json already exists, overwrite it. - - **Clarification Outcomes in Plan**: Incorporate all clarification answers from steps 3–4 into `plan.md` and `tasks.json`: - - Update the relevant task's `requirements` or `description` in `tasks.json` based on the answer. Do NOT create a separate task for an implementation detail—only add a new task when the answer introduces entirely new migration scope. - - Record all clarification questions and their outcomes in the **"## Open Questions & Questionnaire"** section of `plan.md`: - - Answered: `- [x] Q: ... → A: ...` - - Unanswered/skipped: `- [ ] ...` - - Remove the section entirely if no clarification questions were raised. - - **IMPORTANT**: The plan.md should NOT contain the detailed task breakdown. Those details go into tasks.json for better tracking and programmatic access. - - **Task Breakdown Rules**: When creating tasks for tasks.json and plan.md: - - Purpose: Break down coding work into discrete migration tasks. Each task represents a user-requested migration from one service/component to another, or a specific business logic modernization. - - Create tasks ONLY based on what the user explicitly requested - do not infer or add implicit tasks, **except** for the security/CVE remediation task which must always be included in every plan - - If an `assessment-report` is provided, the task description must identify which specific issues from the assessment report are addressed by that task (e.g., "Addresses issues: , ") - - Group related changes that serve a single user goal into one task (e.g., all changes needed to migrate to PostgreSQL) - - Find a matched skill / pattern for the task, following the following priority order. - 1. Skills available for the project, which will be listed in the `skill` tool description. - 2. Patterns that will be attached and available at plan execution phase, listed in the supported patterns file. - 3. Otherwise if no relevant pattern is available for the task pattern, use the prompt segment from the user directly. DO NOT expand the request scope. - - **IMPORTANT**: - - You MUST NOT use the pattern name as the skill name in the generated plan and tasks.json. - - If there are similar skills defined in project skill `.github/skills/` versus other skills, MUST use the one defined in project. - - Skills must be fully matched. For migration scenarios, both the source product and target product must match the task intent. - - Each task should be independently testable with integration tests - - Do not add tests for unimpacted code or existing functionality unless user requested - - **IMPORTANT**: Do NOT read individual skill files at this stage; Do Not include the skill detail in the tasks. - - **Integration Test Task Rules**: When user explicitly requests integration testing (e.g., "add integration tests", "generate integration tests", "test the migration"): - - Add an integration test task with type "integrationTest" after all transform/upgrade tasks but before containerization tasks - - This integration test task should: - - Have id format: "{sequence}-integrationTest" where sequence is the next number after the last migration task (e.g., if last migration is 001, use "002-integrationTest") - - Have description: "Generate and run integration tests for Azure service migrations" - - Have dependencies on ALL transform and upgrade task ids (so it runs after all migrations are complete) - - Have requirements: "Generate Layer 1 (Local Integration with TestContainers) and Layer 2 (Smoke Tests) for all Azure service migrations" - - Have layers: [1, 2] (only Layer 1 and Layer 2 tests) - - Omit environmentConfiguration unless explicitly provided by user input - - The integration test task appears in plan.md as a separate section after migration tasks but before containerization - - **Java Upgrade Task Guidelines**: Only add an upgrade task if the user explicitly requests it. You must refer to the ./java-upgrade-guideline.md for specific rules and guidelines when creating Java upgrade tasks. - - **.NET Upgrade Task Guidelines**: You must refer to the ./dotnet-upgrade-guideline.md for specific rules and guidelines when creating .NET upgrade tasks. - - **Deployment Task Rules**: - - **IMPORTANT** Do NOT create task type with `containerization` if deployment task already exists, deployment task will cover the containerization work if needed. - - Deployment Task Options: Azure App Service, Azure Kubernetes Service, Azure Container Apps (default), Azure App Service Managed Instance, Azure Static Web App, Azure Function App - - **Security Task Guidelines**: The security task order should be after all the upgrade and transform tasks and before the deployment tasks in the generated plan. If the user provides specific security requirements, incorporate them into the security task; otherwise, use the default requirements from the template. - - **IMPORTANT**: The upgrade task must be the first task in the task list because subsequent transform tasks (e.g., migrating to Azure services) depend on the upgraded runtime and project format. - -6. **Rulebook Compliance Validation** (only when rulebook attachments are present): - After generating the plan and tasks, call skill `validate-rulebook-compliance` to validate that the plan tasks cover the rulebook rules: - - tasks-json-path: `${modernization-work-folder}/.metadata/tasks.json` - - compliance-output-path: `${modernization-work-folder}/rulebook-compliance.md` - - This validation is **best-effort only** and must **not** block or fail plan creation. - - If the validation call cannot run, fails, or required context/attachments are missing, you must still complete the workflow and emit `${modernization-work-folder}/plan.md` and `${modernization-work-folder}/.metadata/tasks.json`. - - If validation cannot be completed successfully, write a minimal warning/status report to `${modernization-work-folder}/rulebook-compliance.md` explaining that validation was skipped or failed and why, if known. - -## Completion Criteria - -1. All clarification & questionnaire questions have been asked (or skipped with defaults) via `ask_user`, answers incorporated into `plan.md` and `tasks.json`, and outcomes recorded in the "## Open Questions & Questionnaire" section of `plan.md` -2. The modernization task list is built -3. The modernization task list MUST be scoped according to user input -4. DON'T RUN the plan if user does not explicitly ask you to run the plan -5. The generated plan.md and tasks.json are saved in the specified folder `${modernization-work-folder}` \ No newline at end of file diff --git a/.github/skills/create-modernization-plan/dotnet-upgrade-guideline.md b/.github/skills/create-modernization-plan/dotnet-upgrade-guideline.md deleted file mode 100644 index a5c97e03a..000000000 --- a/.github/skills/create-modernization-plan/dotnet-upgrade-guideline.md +++ /dev/null @@ -1,43 +0,0 @@ -# .NET Upgrade Task Guidelines - -Only add a .NET upgrade task if one of the following conditions is met: - -1. **End of Life (EOL)**: The project's .NET version is out of mainstream support. -2. **Azure SDK Compatibility**: The project targets a .NET Framework version older than 4.6.2, which does not support `netstandard2.0` and cannot use modern Azure SDK (`Azure.*`) packages. -3. **User Request**: The user explicitly requests a .NET version upgrade. - -The upgrade task must be the first task if it exists. - -### Target Version - -Always upgrade to the **latest LTS** version unless the user explicitly specifies a different target version. - -- Current latest LTS: **.NET 10** (`net10.0`) - -### Azure SDK Minimum .NET Version - -The modern Azure SDK for .NET (`Azure.*` packages) targets `netstandard2.0` as its baseline. The following .NET Framework versions do **not** support `netstandard2.0` and require an upgrade: - -| .NET Framework Version | `netstandard2.0` Support | Action | -|------------------------|:------------------------:|--------| -| 4.5 and below | ❌ | Upgrade required | -| 4.6 | ❌ | Upgrade required | -| 4.6.1 | ⚠️ Unreliable | Upgrade recommended | -| 4.6.2+ | ✅ | No upgrade needed for Azure SDK compatibility | - -> **Note**: .NET Framework 4.6.1 is technically listed as supporting `netstandard2.0` but has known issues. Microsoft recommends 4.7.2+ for reliable support. The Azure SDK explicitly targets `net462` as its minimum. - -## Framework Compatibility - -| Source Framework | Target Framework | SDK-Style Conversion Required | -|-----------------|:----------------:|:----------------------------:| -| .NET Framework 4.x | net10.0 | Yes | -| .NET Core 3.1 | net10.0 | No | -| .NET 5–9 | net10.0 | No | - -## .NET Task Selection Rules - -- **Rule 1 — Single task only**: Always create a **single** upgrade task that encompasses all necessary changes. The `modernize-dotnet-upgrade-engineer` agent handles the detailed breakdown during execution. -- **Rule 2 — EOL or Azure SDK incompatibility**: Create task "Upgrade .NET to latest LTS (net10.0)". Set the task `skills` array to `[]` (empty) — the upgrade agent handles execution internally. -- **Rule 3 — User-specified version**: Create task "Upgrade .NET to version X". Set the task `skills` array to `[]` (empty) — the upgrade agent handles execution internally. -- **Rule 4 — No upgrade needed**: If the project's .NET version is in support **and** the project targets .NET Framework 4.6.2+ (or any .NET Core / modern .NET) **and** the user did not request an upgrade, do **not** add an upgrade task. \ No newline at end of file diff --git a/.github/skills/create-modernization-plan/infra-plan-template.md b/.github/skills/create-modernization-plan/infra-plan-template.md deleted file mode 100644 index a0bf419fb..000000000 --- a/.github/skills/create-modernization-plan/infra-plan-template.md +++ /dev/null @@ -1,48 +0,0 @@ -# Infrastructure Plan Template - -Use this template when user explicitly requests infrastructure preparation (e.g., "prepare infrastructure", "create landing zone", "provision resources", "generate Bicep/Terraform/IaC files"). - ---- - -# Infrastructure Plan: [Plan Title] - -## User Requirements - -[A concise summary of the user's inputs, requirements and preferences for the infrastructure, may include: project codes, assessment report, architecture diagram] - -**Plan Configuration**: - -| Parameter | Value | Description | -|-----------|-------|-------------| -| IaC Tool | [bicep (default) / terraform] | Infrastructure as Code tool | -| Provision | [true (default) / false] | Whether to provision resources after generating IaC | -| Subscription | [Azure subscription ID] | Target Azure subscription | - ---- - -## Proposed Architecture - -[A high-level text diagram illustrating the proposed Azure resource architecture that meets the user's requirements] - ---- - -## Azure Resource List -A complete list of Azure resources to be generated. - -| Resource Type | Resource Name | SKU | Purpose | -|---------------|---------------|-----|---------| -| [e.g., SQL Database] | [e.g., sqldb-myapp-prod] | [e.g., S1] | [Purpose] | - ---- - -## Task - -**Description**: Generate IaC files to provision the required Azure resources. - -**Output**: Files of infrastructure as code - -**Skill**: [infrastructure-bicep-generation | infrastructure-terraform-generation] - -**Success Criteria**: -- IaC files generated and validated -- Resources provisioned successfully (if Provision=true) diff --git a/.github/skills/create-modernization-plan/java-upgrade-guideline.md b/.github/skills/create-modernization-plan/java-upgrade-guideline.md deleted file mode 100644 index 270412a96..000000000 --- a/.github/skills/create-modernization-plan/java-upgrade-guideline.md +++ /dev/null @@ -1,44 +0,0 @@ -# Java Upgrade Task Guidelines - -Only add an upgrade task if the user explicitly requests it. The upgrade task must be the first task if it exists. - -## Latest Stable Versions - -- Java: 25 -- Spring Boot: 4.x -- Spring Framework: 7.x - -## Supported Upgrade Versions - -- Java: 11, 17, 21, 25 -- Spring Boot: 3.x, 4.x -- Spring Framework: 6.x, 7.x - -## Framework Compatibility - -| Spring Boot | Spring Framework | Jakarta EE | Minimum Java | Maximum Java | -|-------------|:----------------:|:----------:|:------------:|:------------:| -| 2.x | 5.x | JavaEE (javax.*) | 8 | 11 | -| 3.x | 6.x | Jakarta EE (jakarta.*) | 17 | 21 | -| 4.x | 7.x | Jakarta EE (jakarta.*) | 25 | 25 | - -## Upgrade Task Types and Included Changes - -| Task Type | Spring Framework Upgrade | Jakarta EE Migration (javax.* → jakarta.*) | JDK/Java | -|-----------|:------------------------:|:------------------------------------------:|:-----------:| -| Spring Boot 4.x upgrade | 7.x | ✓ | 25 | -| Spring Boot 3.x upgrade | 6.x | ✓ | 21 | -| Spring Framework 7.x upgrade | — | ✓ | 25 | -| Spring Framework 6.x upgrade | — | ✓ | 21 | -| Jakarta EE upgrade | — | ✓ | 21 | -| JDK/Java upgrade | — | — | to specified version | - -## Java Task Selection Rules - -When selecting the Java upgrade task type, follow these rules in order: - -- **Rule 1 — No redundant sub-tasks**: Each upgrade type (Spring Boot, Spring Framework, Jakarta EE, JDK/Java) is hierarchical — higher-level tasks already include lower-level ones. Never create a lower-level task that is already covered by a selected higher-level task. For example, if a Spring Boot 4.x upgrade task is selected (which already includes JDK 25), do NOT also create a separate JDK/Java upgrade task. -- **Rule 2 — User-specified framework request doesn't fully match system state**: When the user requests a **framework** upgrade (e.g., Spring Boot, Spring Framework) but the target version they specify is not the latest available, select the highest-level task applicable and prompt the user to clarify. For example, if the user asks to "upgrade Spring Boot to 3.x" but Spring Boot 4.x is available, create a Spring Boot 3.x upgrade task and add a clarification question asking whether they want 4.x. **This rule does NOT apply to pure JDK/Java upgrade requests — see Rule 4.** -- **Rule 3 — User-specified request matches system state**: Select the most closely matching task type that directly matches the user's request and fits the system. For example, if the user asks to "upgrade JDK" and the JDK is outdated, create a JDK/Java upgrade task — NOT a higher-level Spring Boot or Spring Framework upgrade task. -- **Rule 4 — Never upgrade other frameworks on a JDK/Java request**: When the user explicitly requests a JDK/Java upgrade, you MUST NOT upgrade Spring Boot, Spring Framework, or Jakarta EE — create only a JDK/Java upgrade task targeting the user-specified version. If the project's existing Spring Boot, Spring Framework, or Jakarta EE versions are incompatible with the target Java version (per the Framework Compatibility table above), add a clarification question asking the user whether they also want to upgrade the incompatible framework(s) to a compatible version. -- **Rule 5 — Ask for clarification when the selected task diverges from the user's request**: Whenever the task you create differs from what the user explicitly asked for (e.g., upgrading to a different version, choosing a higher-level task type, or skipping a requested change due to compatibility), you MUST use the `ask_user` tool to explain what was selected, why it differs, and ask the user to confirm or adjust. If `ask_user` is not available, add the question to the "## Open Questions" section of `plan.md`. Never silently override the user's intent. \ No newline at end of file diff --git a/.github/skills/create-modernization-plan/plan-template.md b/.github/skills/create-modernization-plan/plan-template.md deleted file mode 100644 index 907521214..000000000 --- a/.github/skills/create-modernization-plan/plan-template.md +++ /dev/null @@ -1,70 +0,0 @@ -# Modernization Plan Template - -Use this template to generate modernization plans for applications. Replace placeholders with actual values and customize content based on the specific modernization scenario. - -**Planning Philosophy**: -- **Focus on GOALS, not implementation**: Describe WHAT needs to be achieved from the user's perspective -- **Skills define HOW**: All implementation details (frameworks, SDKs, libraries, code patterns, authentication methods) will be determined by the referenced skills -- **No assumptions**: Do not assume or specify any technical implementation approach in the plan - let skills handle all "how" decisions - ---- - -# Modernization Plan: [Modernization Title] - -**Project**: [Application Name] - ---- - -## Technical Framework - -**Purpose**: Document the application's current technology stack to provide context for the migration. - -**Template**: -```markdown -- **Language**: [Programming language and version, e.g., Java 11, Python 3.9, .NET 6] -- **Framework**: [Application framework and version, e.g., Spring Boot 2.7.18, Django 4.2, ASP.NET Core 6.0] -- **Build Tool**: [Build system, e.g., Maven 3.9, Gradle 8.0, npm] -- **Database**: [Current database, e.g., Oracle 19c, PostgreSQL 14, SQL Server 2019] -- **Key Dependencies**: [Major libraries/frameworks, e.g., Spring Data JPA, Hibernate, Entity Framework] -``` - ---- - -## Overview - -**Purpose**: Describe the high-level modernization goals without technical details. Focus on business objectives and what will change. - -**Template**: -> This migration [describe what is being migrated]. The application currently [describe current state]. The new architecture will: -> -> - [First key change and its business benefit] -> - [Second key change and its business benefit] -> - [Third key change and its business benefit] -> -> The migration follows [describe phased approach without technical specifics]. - ---- - -## Migration Impact Summary - -**Purpose**: Create a simple table showing migration impact for each application and its affected services. -**Rule**: Each line should be limited to a maximum of 80 characters. -**Template**: -``` -| Application | Original Service | New Azure Service | Authentication | Comments | -|-------------|------------------|-------------------|-------------|----------------|----------| -| [App Name] | [Old Service] | [Azure Service] | [Auth method (Default is Managed Identity if user not specified)] | [User specified request for the migration] | -``` - ---- - -## Open Questions & Questionnaire - -**Purpose**: Record all clarification questions raised during plan creation and their resolution status. Record all answers to questionnaire questions. - -**Template**: -```markdown -- [x] Q: What authentication method should be used for Azure Storage? → A: Use managed identity -- [ ] Which region should the deployment target? -``` - diff --git a/.github/skills/create-modernization-plan/questionnaire.md b/.github/skills/create-modernization-plan/questionnaire.md deleted file mode 100644 index 7a87a9bfa..000000000 --- a/.github/skills/create-modernization-plan/questionnaire.md +++ /dev/null @@ -1,19 +0,0 @@ -# Try to answer these from the user context, and otherwise ask the user where required. - -1. Deployment to Azure -* Azure Container Apps (must include containerization) -* Azure Kubernetes Service (must include containerization) -* Azure App Service -* Azure App Service Managed Instance -* Azure Function App -* Azure Static Web App -* (Default) No deployment - -2. Should the Modernization Plan include Integration Testing? -* Yes, Local Integration with Containers -* Yes, Local Integration and Smoke Tests -* (Default) No - -3. Should the Modernization Plan include Containerization (Dockerfile generation)? -* Yes -* (Default) No \ No newline at end of file diff --git a/.github/skills/create-modernization-plan/security-plan-template.md b/.github/skills/create-modernization-plan/security-plan-template.md deleted file mode 100644 index 3a641b72a..000000000 --- a/.github/skills/create-modernization-plan/security-plan-template.md +++ /dev/null @@ -1,26 +0,0 @@ -## Security Compliance - -**Purpose**: Scan and remediate CVEs (Common Vulnerabilities and Exposures) in project dependencies to ensure the modernized application is free of known security vulnerabilities. - -**Condition**: Always include this task in every modernization plan. Do not include this task if the user explicitly requests that it be removed. - -**Template**: - -**Description**: Scan all project dependencies for known CVEs and remediate any identified vulnerabilities to ensure the application is secure before deployment. - -**Requirements**: - Upgrade vulnerable dependencies to the minimum patched version. If a CVE fix requires a major version upgrade, document the affected dependency, the current version, the upgraded major version, and the breaking change risk. Verify that the project builds and all tests pass after remediation. - If the user provided specific security requirements, incorporate them as well. - -**Environment Configuration**: - Runtime environment established by previous tasks (e.g., Java Home, .NET runtime). - Build tool established by previous tasks (e.g., Maven/Gradle, dotnet). - -**App Scope**: - The app folders that this task will operate on - -**Skills**: - - Skill Name: validate-cves-and-fix - - Skill Location: builtin - - Skill Name: [additional skill if needed] - - Skill Location: [Skill location] \ No newline at end of file diff --git a/.github/skills/create-modernization-plan/supported-patterns-dotnet.md b/.github/skills/create-modernization-plan/supported-patterns-dotnet.md deleted file mode 100644 index 61212775b..000000000 --- a/.github/skills/create-modernization-plan/supported-patterns-dotnet.md +++ /dev/null @@ -1,50 +0,0 @@ -## Supported Task Patterns - -The following are the task patterns supported by the modernize CLI. These patterns are used to identify the modernization tasks that need to be performed based on the user's input. - -The patterns are categorized into two groups, and they should be treated differently if picked: - -* Patterns with skill definitions: These patterns have pre-defined skills that can be used to execute the tasks. If a task matches one of these patterns, the corresponding skill should be used in the task plan. -* Patterns without skill definitions: These patterns do not have pre-defined skills. If a task matches one of these patterns, the description should be used to guide the AI in performing the required tasks. - **IMPORTANT**: The pattern name should NEVER be used as the skill name in the generated plan and tasks.json. They are meant to guide the task generation, not to be directly used as skills. - - -### Task Patterns with Skill Definitions -These patterns have pre-defined skills to assist in their execution. When they are selected in a modernization plan, the corresponding skills should be used. -Each of the item is written in the following format: `- **skill-name**: skill-description`. - -- **azcli-aks-deploy**: Generate plan for deploying to existing Azure Resources for Azure Kubernetes Service, using azcli -- **azcli-appservice-deploy**: Deployment steps for Azure App Service under the AzCLI flow -- **azcli-appservicemi-deploy**: Deployment steps for Azure App Service Managed Identity under the AzCLI flow -- **azcli-containerapp-deploy**: Generate plan for deploying to existing Azure Resources for Azure Container Apps, using azcli -- **azcli-functionapp-deploy**: Deployment steps for Azure Function App under the AzCLI flow -- **containerization**: Setup Dockerfiles for the project to run inside of containers for Azure Container Apps or Azure Kubernetes Service. -- **infrastructure-bicep-generation**: Generate Bicep IaC files for Azure infrastructure provisioning -- **infrastructure-terraform-generation**: Generate Terraform IaC files for Azure infrastructure provisioning -- **migration-azure-communication-email**: This knowledge base provides knowledge about how to use Azure Communication Services for sending emails in .NET applications, covering authentication with Managed Identity, configuration, and email operations. -- **migration-azure-confluent-kafka**: This knowledge base provides knowledge about migrating .NET applications using Confluent.Kafka from local Kafka to Confluent Cloud on Azure, using Azure Managed Identity and DefaultAzureCredential. -- **migration-azure-database-postgresql**: This file provides guidance for migrating .NET applications to Azure Database for PostgreSQL with passwordless Managed Identity, covering both Entity Framework Core and non-EF Core applications. -- **migration-azure-eventhubs-kafka**: This knowledge base provides knowledge about migrating .NET applications using Confluent.Kafka from local Kafka to Azure Event Hubs for Kafka, using Azure Managed Identity and DefaultAzureCredential. -- **migration-azure-keyvault-certificate**: This file provides guidance on using Azure Key Vault certificate management in .NET, covering authentication, configuration, and certificate operations. -- **migration-azure-keyvault-secret**: This file provides guidance on using Azure Key Vault secrets in .NET, covering authentication, configuration, and secret operations. -- **migration-azure-redis-cache**: This knowledge base provides guidance for adding distributed caching with Azure Cache for Redis to .NET applications, or migrating existing caching implementations (in-memory cache, Redis, or other providers) to use Azure Cache for Redis with DefaultAzureCredential (Managed Identity) authentication. -- **migration-azure-servicebus**: This knowledge base provides knowledge about migrating .NET applications to use Azure Service Bus with Managed Identity, covering queue and topic operations, message handling, and processor configurations. -- **migration-azure-sql-database**: This knowledge base provides knowledge about migrating .NET applications to use Azure SQL Database and Azure SQL Managed Instance with Managed Identity authentication, including Entity Framework 6 provider configuration. -- **migration-azure-storage-blob**: This knowledge base provides comprehensive knowledge about using Azure Storage Blob SDK in .NET applications, covering Managed Identity authentication, blob operations, container management, and SAS token generation. -- **migration-azure-storage-mount**: This knowledge base provides knowledge about migrating .NET applications from using hard-coded local file paths to Azure mounted storage paths while maintaining the same functionality. -- **migration-console-logging**: This knowledge base provides knowledge about configuring console logging in .NET applications for cloud environments, ensuring proper log output for container and cloud platform log aggregation. -- **migration-dependency-management**: Comprehensive guide for understanding and implementing dependency management in .NET projects, covering both modern SDK-style projects (.NET Core, .NET 5+) and legacy .NET Framework projects. Includes package reference management, project file structure, and migration considerations. -- **migration-managed-identity**: Comprehensive guidance for migrating .NET applications to use Azure Managed Identity for authentication instead of connection strings, client secrets, certificates, or other credential-based authentication methods. Covers Azure SQL, Storage, Key Vault, Service Bus, Event Hubs, Cosmos DB, and more. -- **migration-microsoft-entra-id**: This knowledge base provides knowledge about migrating .NET applications to use Microsoft Entra ID (formerly Azure AD) for authentication, including ASP.NET Core, ASP.NET Web Forms, and Microsoft Graph integration. -- **migration-opentelemetry-azure**: This guide describes how to implement OpenTelemetry in .NET with Azure Monitor for tracing, metrics, and logging migration. - -### Task Patterns without Skill Definitions -These patterns DO NOT have pre-defined skills. The pattern name and description define the modernization scenario, NOT A SKILL. They are in the format of `- **pattern-name**: pattern-description`. - -A pattern should be selected if it matches one of the customer's requirements, and there are no skills supporting this requirement. - -**IMPORTANT**: -- NEVER write the pattern name as skill name in the generated plan. -- Tasks generated from these patterns must have NO skill assigned. Do not reuse any skill from the "Task Patterns with Skill Definitions" section, even if a skill targets a similar technology or appears related. - - diff --git a/.github/skills/create-modernization-plan/supported-patterns-java.md b/.github/skills/create-modernization-plan/supported-patterns-java.md deleted file mode 100644 index 6ae2dee44..000000000 --- a/.github/skills/create-modernization-plan/supported-patterns-java.md +++ /dev/null @@ -1,88 +0,0 @@ -## Supported Task Patterns - -The following are the task patterns supported by the modernize CLI. These patterns are used to identify the modernization tasks that need to be performed based on the user's input. - -The patterns are categorized into two groups, and they should be treated differently if picked: - -* Patterns with skill definitions: These patterns have pre-defined skills that can be used to execute the tasks. If a task matches one of these patterns, the corresponding skill should be used in the task plan. -* Patterns without skill definitions: These patterns do not have pre-defined skills. If a task matches one of these patterns, the description should be used to guide the AI in performing the required tasks. - **IMPORTANT**: The pattern name should NEVER be used as the skill name in the generated plan and tasks.json. They are meant to guide the task generation, not to be directly used as skills. - - -### Task Patterns with Skill Definitions -These patterns have pre-defined skills to assist in their execution. When they are selected in a modernization plan, the corresponding skills should be used. -Each of the item is written in the following format: `- **skill-name**: skill-description`. - -- **azcli-aks-deploy**: Generate plan for deploying to existing Azure Resources for Azure Kubernetes Service, using azcli -- **azcli-appservice-deploy**: Deployment steps for Azure App Service under the AzCLI flow -- **azcli-appservicemi-deploy**: Deployment steps for Azure App Service Managed Identity under the AzCLI flow -- **azcli-containerapp-deploy**: Generate plan for deploying to existing Azure Resources for Azure Container Apps, using azcli -- **azcli-functionapp-deploy**: Deployment steps for Azure Function App under the AzCLI flow -- **containerization**: Setup Dockerfiles for the project to run inside of containers for Azure Container Apps or Azure Kubernetes Service. -- **infrastructure-bicep-generation**: Generate Bicep IaC files for Azure infrastructure provisioning -- **infrastructure-terraform-generation**: Generate Terraform IaC files for Azure infrastructure provisioning -- **migration-AWS-secrets-manager-to-azure-key-vault**: Migrates Java applications from AWS Secrets Manager (SecretsManagerClient, AWSSecretsManager) to Azure Key Vault SecretClient for secrets management. Use when migrating Java projects from AWS to Azure, replacing AWS secret storage with Azure Key Vault, or modernizing cloud secret management. -- **migration-activemq-servicebus**: Migrates Java Spring Boot applications from ActiveMQ JMS messaging to Azure Service Bus JMS. Replaces ActiveMQ dependencies with Spring Cloud Azure Service Bus JMS starter and updates connection configuration. Use when migrating Spring JMS applications from ActiveMQ to Azure Service Bus or modernizing on-premises messaging to Azure. -- **migration-amqp-rabbitmq-servicebus**: Migrates Java Spring applications from RabbitMQ AMQP messaging to Azure Service Bus via Spring Messaging. Replaces Spring AMQP RabbitMQ dependencies, updates connection settings, and migrates message producers and consumers. Use when migrating Spring applications from RabbitMQ to Azure Service Bus or replacing on-premises message brokers with Azure managed messaging. -- **migration-ant-project-to-maven-project**: Migrates Java projects from Ant build system to Maven, converting build.xml to pom.xml and restructuring directories to Maven standard layout. Use when modernizing Java projects that use Ant builds, converting to Maven for dependency management, or standardizing build tooling. -- **migration-auth-by-mi-for-azure-redis-in-micronaut-project**: Secure Azure Cache for Redis with Managed Identity via Micronaut -- **migration-certificate-management-to-azure-key-vault**: Migrates Java TLS/MTLS certificate management from local KeyStore storage to Azure Key Vault JCA (Java Cryptography Architecture). Replaces local certificate handling with Azure Key Vault for centralized certificate management. Use when migrating Java applications that use local KeyStore, SSLContext, or Certificate classes to Azure Key Vault for secure certificate storage. -- **migration-confluent-cloud-kafka**: Migrates Java applications from self-hosted Apache Kafka to Apache Kafka on Confluent Cloud with passwordless authentication via Microsoft Entra ID. Updates Kafka connection configuration and authentication settings. Use when migrating Java Kafka producers or consumers to Confluent Cloud or enabling passwordless Entra ID authentication for Kafka on Confluent Cloud. -- **migration-cryptography-operations-to-azure-key-vault**: Migrates Java cryptographic operations (Cipher, Signature) from local key management to Azure Key Vault for centralized key management and cryptographic operations. Use when migrating Java applications that use javax.crypto.Cipher or java.security.Signature to Azure Key Vault, or centralizing cryptographic key management in Azure. -- **migration-eclipse-project-to-maven-project**: Migrates Java projects from Eclipse IDE project format (.project, .classpath) to Apache Maven project structure with pom.xml. Converts Eclipse build configuration and classpath settings to Maven conventions. Use when modernizing Eclipse-based Java projects to Maven, converting .project/.classpath files, or standardizing build tooling. -- **migration-ibm-db2-to-azure-postgresql**: Migrate IBM Db2 to Azure Database for PostgreSQL -- **migration-ibm-db2-to-azure-sql**: Migrate IBM Db2 to Azure SQL Database -- **migration-informix-to-postgresql**: Migrates Java application database layer from Informix Database to PostgreSQL, including JDBC driver changes, SQL syntax conversion, and Informix-specific feature replacement. Use when migrating Java applications from Informix to PostgreSQL, converting Informix SQL to PostgreSQL syntax, or replacing Informix JDBC drivers. -- **migration-java-ee-amqp-rabbitmq-servicebus**: Migrates Java EE/Jakarta EE applications from RabbitMQ AMQP messaging to Azure Service Bus SDK. Replaces RabbitMQ client dependencies, migrates publishers and consumers, updates connection management, and maps RabbitMQ concepts (exchanges, queues) to Service Bus equivalents (topics, subscriptions). Use when migrating Java EE or Jakarta EE applications from RabbitMQ to Azure Service Bus. -- **migration-javax.email-send-to-azure-communication-service-email**: Migrates Java applications from JavaMail (javax.mail) API to Azure Communication Service Email for sending emails. Replaces JavaMail email sending, message construction, and authentication with Azure Communication Service equivalents. Use when migrating Java applications from JavaMail or SMTP-based email sending to Azure Communication Service Email. -- **migration-jax-rpc-to-jax-ws**: Migrates Java web service implementations from deprecated JAX-RPC to JAX-WS. Updates web service configuration files and JAX-RPC specific code to use JAX-WS equivalents. Use when modernizing Java web services that use JAX-RPC or upgrading deprecated JAX-RPC APIs to the JAX-WS standard. -- **migration-kafka-to-eventhubs**: Migrates Java applications from Kafka to Azure Event Hubs for Kafka with managed identity for secure, passwordless authentication. Updates Spring Cloud Azure dependencies, Kafka connection settings, and authentication configuration. Use when migrating Java Kafka producers or consumers to Azure Event Hubs, or enabling managed identity authentication for event streaming. -- **migration-log-to-console**: Migrates Java application logging from file-based output to console-only output. Removes file appenders from logging configuration (logback, logging.xml) and ensures all log output goes to console. Use when containerizing Java applications, migrating to cloud-native logging, or preparing Java apps for Azure where console logging is preferred. -- **migration-mi-azure-sql**: Migrates Java Spring Boot projects from password-based authentication to Azure Managed Identity for connecting to Azure SQL Database. Updates Spring Cloud Azure dependencies and datasource configuration for passwordless authentication. Use when enabling managed identity for Azure SQL Database connections, removing hardcoded database passwords, or securing Java Spring Boot database authentication. -- **migration-mi-cassandra**: Migrates Java applications to connect to Azure Cosmos DB for Apache Cassandra using managed identity via Service Connector in Azure public cloud. Updates CqlSession configuration and Spring Data Cassandra properties. Use when migrating Java or Spring Boot applications to Azure Cosmos DB Cassandra API, enabling managed identity for Cassandra connections, or replacing Cassandra password authentication. -- **migration-mi-eventhub**: Migrates Java projects from password-based authentication to Azure Managed Identity for connecting to Azure Event Hubs. Adds Spring Cloud Azure dependencies and updates Event Hubs and Kafka configuration for passwordless authentication. Use when enabling managed identity for Azure Event Hubs in Java applications, removing Event Hubs connection string passwords, or securing event streaming authentication. -- **migration-mi-mariadb**: Migrates Java applications from password-based MariaDB authentication to Azure Managed Identity for Azure Database for MariaDB in public cloud using AzureMysqlAuthenticationPlugin. Updates JDBC connection and authentication configuration. Use when enabling managed identity for Azure Database for MariaDB, removing MariaDB passwords, or implementing credential-free MariaDB authentication in Azure. -- **migration-mi-mongodb**: Migrate from password-based authentication to Microsoft Entra ID authentication for Azure DocumentDB (with MongoDB Compatibility) in Java projects -- **migration-mi-mysql**: Migrates Java Spring Boot projects from password-based MySQL authentication to Azure Managed Identity for Azure Database for MySQL. Updates Spring Cloud Azure dependencies and datasource configuration for passwordless authentication. Use when enabling managed identity for Azure MySQL connections in Spring Boot, removing hardcoded MySQL passwords, or securing Spring datasource authentication. -- **migration-mi-postgresql**: Migrates Java Spring Boot projects from password-based PostgreSQL authentication to Azure Managed Identity for Azure Database for PostgreSQL. Updates Spring Cloud Azure dependencies and datasource configuration for passwordless authentication. Use when enabling managed identity for Azure PostgreSQL connections in Spring Boot, removing hardcoded PostgreSQL passwords, or securing Spring datasource authentication. -- **migration-mi-servicebus**: Azure Service Bus Managed Identity via Spring -- **migration-on-premises-user-authentication-to-microsoft-entra-id**: Migrates Java Spring Boot application user authentication from on-premises login to Microsoft Entra ID using spring-cloud-azure-starter-active-directory and spring-boot-starter-oauth2-client. Use when migrating Java web application authentication to Microsoft Entra ID, modernizing on-premises login to cloud identity, or adding Azure Active Directory authentication. -- **migration-oracle-to-postgresql**: Migrates Java application database layer from Oracle Database to PostgreSQL, including JDBC driver changes, SQL syntax conversion, and Oracle-specific feature replacement. Uses project-specific coding_notes.md guidance when available. Use when migrating Java applications from Oracle to PostgreSQL, converting Oracle SQL to PostgreSQL syntax, or replacing Oracle JDBC drivers. -- **migration-other-cache-solutions-to-azure-managed-cache**: Migrate other cache solutions to use Redis, and potentially to Azure Managed Redis / Azure Cache for Redis (retiring) while following best practices. Use this skill when users want to migrate other cache solutions to use Redis, such as Apache Commons JCS, DynaCache, Embedded cache, JCache, OSCache, ShiftOne, Oracle Coherence, etc., or local Redis to Azure Managed Redis / Azure Cache for Redis (retiring) with secure authentication changes. -- **migration-plaintext-credential-to-azure-keyvault**: Migrates hardcoded plaintext credentials (passwords, secrets, API keys, connection strings, tokens) in Java source code to Azure Key Vault for secure storage and retrieval. Use when securing Java applications by removing hardcoded credentials, migrating plaintext secrets to Azure Key Vault, or implementing centralized secret management. -- **migration-s3-to-azure-blob-storage**: Migrates Java applications from Amazon S3 SDK to Azure Blob Storage SDK, including bucket/container operations, object storage, access policies, and SAS token generation. Use when migrating Java applications from AWS S3 to Azure Blob Storage, replacing S3Client with BlobServiceClient, or converting AWS storage APIs to Azure equivalents. -- **migration-spring-jms-rabbitmq-servicebus**: Migrates Java Spring Boot applications from RabbitMQ JMS messaging to Azure Service Bus JMS. Replaces RabbitMQ JMS dependencies with Spring Cloud Azure Service Bus JMS starter and updates connection configuration. Use when migrating Spring JMS applications from RabbitMQ to Azure Service Bus, replacing RMQConnectionFactory, or modernizing JMS messaging to Azure. -- **migration-sqs-to-servicebus**: Migrates Java applications from AWS Simple Queue Service (SQS) to Azure Service Bus for message queuing. Replaces AWS SQS SDK dependencies with Azure Service Bus SDK, updates message sending and receiving code, and migrates queue configuration. Use when migrating Java applications from AWS SQS to Azure Service Bus, replacing SQS client code, or modernizing cloud message queuing to Azure. -- **migration-sybase-ase-to-azure-sql-database**: Migrates Java application database layer from Sybase ASE (Adaptive Server Enterprise) to Azure SQL Database with passwordless managed identity authentication. Use when migrating Java applications from Sybase ASE to Azure SQL. - -### Task Patterns without Skill Definitions -These patterns DO NOT have pre-defined skills. The pattern name and description define the modernization scenario, NOT A SKILL. They are in the format of `- **pattern-name**: pattern-description`. - -A pattern should be selected if it matches one of the customer's requirements, and there are no skills supporting this requirement. - -**IMPORTANT**: -- NEVER write the pattern name as skill name in the generated plan. -- Tasks generated from these patterns must have NO skill assigned. Do not reuse any skill from the "Task Patterns with Skill Definitions" section, even if a skill targets a similar technology or appears related. - -- **amazon-kinesis-to-azure-event-hubs**: Amazon Kinesis to Azure Event Hubs -- **amazon-sns-to-azure-service-bus**: Amazon SNS to Azure Service Bus -- **apache-pulsar-to-azure-event-hubs**: Apache Pulsar to Azure Event Hubs -- **aws-lambda-to-azure-functions**: AWS Lambda to Azure Functions -- **firebird-to-azure-postgresql**: Firebird to Azure PostgreSQL -- **google-cloud-bigtable-to-azure-cosmos-db**: Google Cloud Bigtable to Azure Cosmos DB -- **google-cloud-functions-to-azure-functions**: Google Cloud Functions to Azure Functions -- **google-cloud-pub-sub-to-azure-service-bus**: Google Cloud Pub/Sub to Azure Service Bus -- **google-cloud-spanner-to-azure-postgresql**: Google Cloud Spanner to Azure PostgreSQL -- **google-cloud-storage-to-azure-blob-storage**: Google Cloud Storage to Azure Blob Storage -- **google-firestore-to-azure-cosmos-db**: Google Firestore to Azure Cosmos DB -- **ibm-db2-to-azure-postgresql**: IBM DB2 to Azure PostgreSQL -- **ibm-mq-jms-to-azure-service-bus**: IBM MQ JMS to Azure Service Bus -- **migration-local-certificate-management-to-azure-key-vault**: Local certificate management to Azure Key Vault -- **migration-local-files-to-mounted-azure-storage**: Local files to mounted Azure Storage paths (starts with `${AZURE_MOUNT_PATH:/mnt/azure}`) -- **quartz-scheduler-to-azure-functions**: Quartz Scheduler to Azure Functions -- **solace-pubsub-to-azure-service-bus**: Solace PubSub+ to Azure Service Bus -- **spring-batch-to-azure-durable-functions**: Spring Batch to Azure Durable Functions -- **spring-cloud-config-to-azure-app-configuration**: Spring Cloud Config to Azure App Configuration -- **sqlite-to-azure-postgresql**: SQLite to Azure PostgreSQL -- **sybase-ase-to-azure-postgresql**: Sybase ASE to Azure Database for PostgreSQL -- **tibco-ems-jms-to-azure-service-bus**: TIBCO EMS JMS to Azure Service Bus diff --git a/.github/skills/create-modernization-plan/tasks-schema.json b/.github/skills/create-modernization-plan/tasks-schema.json deleted file mode 100644 index 397678e03..000000000 --- a/.github/skills/create-modernization-plan/tasks-schema.json +++ /dev/null @@ -1,349 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Modernization Tasks Template", - "description": "Schema for tasks-template.json. Preserve field descriptions because they are used to generate the JSON by LLM.", - "type": "object", - "required": ["$schema", "description", "tasks", "metadata"], - "additionalProperties": false, - "properties": { - "$schema": { - "type": "string", - "description": "JSON Schema reference used by the template." - }, - "description": { - "type": "string", - "description": "Tasks template for modernization plan. Generate this file alongside plan.md to track individual migration tasks." - }, - "tasks": { - "type": "array", - "description": "List of individual migration tasks.", - "items": { - "oneOf": [ - { "$ref": "#/$defs/transformTask" }, - { "$ref": "#/$defs/upgradeTask" }, - { "$ref": "#/$defs/integrationTestTask" }, - { "$ref": "#/$defs/containerizationTask" }, - { "$ref": "#/$defs/deploymentTask" }, - { "$ref": "#/$defs/securityTask" }, - { "$ref": "#/$defs/infrastructureTask" } - ] - } - }, - "metadata": { - "type": "object", - "description": "Metadata for the plan.", - "additionalProperties": false, - "required": ["planName", "projectName", "language", "createdAt", "version"], - "properties": { - "planName": { - "type": "string", - "description": "[Plan name]" - }, - "projectName": { - "type": "string", - "description": "[Application Name]" - }, - "language": { - "type": "string", - "description": "[Programming language]" - }, - "createdAt": { - "type": "string", - "description": "[ISO 8601 timestamp]", - "format": "date-time" - }, - "version": { - "type": "string", - "description": "Version of the template schema.", - "const": "1.0" - } - } - } - }, - "$defs": { - "taskBase": { - "type": "object", - "additionalProperties": false, - "required": ["type", "id", "description", "requirements"], - "properties": { - "type": { - "type": "string", - "description": "Task type identifier." - }, - "id": { - "type": "string", - "description": "[Unique identifier for this task, start with sequence and category name, like '001-transform-migration-rabbitmq-to-servicebus']" - }, - "description": { - "type": "string", - "description": "[Brief description of what this task achieves from the user's perspective. It should describe high level requirements without dictating implementation details like API calls, package names, or code structure]" - }, - "reason": { - "type": "string", - "description": "[Explain why this task is needed, e.g., the motivation, business justification, or technical necessity that drives this task]" - }, - "requirements": { - "type": "string", - "description": "[The specific requirements for this task including: what to migrate (features, APIs, patterns), constraints (backward compatibility, modules to avoid), target Azure services, and technical preferences (authentication methods, patterns). It should describe high level requirements without dictating implementation details like API calls, package names, or code structure]" - }, - "environmentConfiguration": { - "type": ["string"], - "description": "[Environment configuration from user input (e.g. endpoint, access id). Omit this field if not specified]" - }, - "status": { - "type": ["string"], - "description": "Task execution status. Only has value after task is executed.", - "enum": ["pending", "started", "success", "failed", "skipped"] - }, - "taskSummary": { - "type": "string", - "description": "Summary of task execution result. Only has value after task is executed." - }, - "dependencies": { - "type": "array", - "description": "[List of task IDs that this task depends on. The task will only be executed after all its dependencies have completed successfully.]", - "items": { - "type": "string" - } - } - } - }, - "skill": { - "type": "object", - "additionalProperties": false, - "required": ["name", "location"], - "properties": { - "name": { - "type": "string", - "description": "[The skill name that will be used for this task, e.g., 'migration-rabbitmq-to-servicebus'.]" - }, - "location": { - "type": "string", - "description": "Skill location: project, remote and builtin, builtin is renamed from custom", - "enum": ["project","remote","builtin"] - } - } - }, - "successCriteria": { - "type": "object", - "additionalProperties": false, - "required": [ - "passBuild", - "generateNewUnitTests", - "passUnitTests" - ], - "description": "The task success criteria to validate after task execution.", - "properties": { - "passBuild": { - "type": ["string"], - "default": "true", - "description": "Project must compile successfully after migration, use default value if user does not specify" - }, - "generateNewUnitTests": { - "type": ["string"], - "default": "false", - "description": "Create mock-based unit tests for newly added Azure integration code to ensure test coverage, use default value if user does not specify" - }, - "passUnitTests": { - "type": ["string"], - "default": "true", - "description": "All unit tests must pass; mock dependent Azure resources if not provided, use default value if user does not specify" - } - } - }, - "successCriteriaStatus": { - "type": "object", - "not": { - "type": "null" - }, - "additionalProperties": false, - "description": "Optional validation status for each success criterion. Omit this field until validation is complete. If present, use true/false string values.", - "properties": { - "passBuild": { - "type": ["string"], - "description": "Validation status of passBuild criterion. true means passed, false means failed." - }, - "generateNewUnitTests": { - "type": ["string"], - "description": "Validation status of generateNewUnitTests criterion. true means passed, false means failed." - }, - "passUnitTests": { - "type": ["string"], - "description": "Validation status of passUnitTests criterion. true means passed, false means failed." - } - } - }, - "transformTask": { - "allOf": [ - { "$ref": "#/$defs/taskBase" }, - { - "type": "object", - "additionalProperties": false, - "required": ["type", "successCriteria"], - "properties": { - "type": { - "const": "transform", - "description": "transform task template" - }, - "skills": { - "type": "array", - "description": "The skills that will be used for this task, it is started with migration and looks like 'migration-rabbitmq-to-servicebus-mi'", - "items": { "$ref": "#/$defs/skill" } - }, - "successCriteria": { "$ref": "#/$defs/successCriteria" }, - "successCriteriaStatus": { "$ref": "#/$defs/successCriteriaStatus" } - } - } - ] - }, - "upgradeTask": { - "allOf": [ - { "$ref": "#/$defs/taskBase" }, - { - "type": "object", - "additionalProperties": false, - "required": ["type", "successCriteria"], - "properties": { - "type": { - "const": "upgrade", - "description": "Upgrade task template" - }, - "successCriteria": { "$ref": "#/$defs/successCriteria" }, - "successCriteriaStatus": { "$ref": "#/$defs/successCriteriaStatus" } - } - } - ] - }, - "containerizationTask": { - "allOf": [ - { "$ref": "#/$defs/taskBase" }, - { - "type": "object", - "additionalProperties": false, - "required": ["type", "dockerfilePath"], - "properties": { - "type": { - "const": "containerization", - "description": "Containerization task template - Only include if the target deployment requires containerization (e.g., AKS, ACA) or if the user explicitly requested containerization. Skip for non-containerized deployments." - }, - "dockerfilePath": { - "type": "string", - "description": "[Path to Dockerfile, indicate if existing or to be created]" - } - } - } - ] - }, - "deploymentTask": { - "allOf": [ - { "$ref": "#/$defs/taskBase" }, - { - "type": "object", - "additionalProperties": false, - "required": ["type", "targetAzureService", "resourceStatus", "deploymentTool", "skills"], - "properties": { - "type": { - "const": "deployment", - "description": "Deployment task template - Containerize, provision and deploy the application to Azure. Only include if the user explicitly requested deployment." - }, - "skills": { - "type": "array", - "description": "The deployment skill to use based on targetAzureService: 'azcli-aks-deploy' for Azure Kubernetes Service, 'azcli-containerapp-deploy' for Azure Container Apps, 'azcli-appservice-deploy' for Azure App Service, 'azcli-functionapp-deploy' for Azure Function App, 'azcli-appservicemi-deploy' for Azure App Service with Managed Identity", - "items": { "$ref": "#/$defs/skill" } - }, - "targetAzureService": { - "type": "string", - "description": "[Azure service name, e.g., Azure Container Apps, AKS, App Service]" - }, - "resourceStatus": { - "type": "string", - "description": "[Specify if using existing resource or will create new service]" - }, - "deploymentTool": { - "type": "string", - "description": "Infrastructure as Code tool type. Default to 'terraform' for aks, 'bicep' for other services if not specified.", - "enum": ["bicep", "terraform"] - } - } - } - ] - }, - "integrationTestTask": { - "allOf": [ - { "$ref": "#/$defs/taskBase" }, - { - "type": "object", - "additionalProperties": false, - "required": ["type", "layers"], - "properties": { - "type": { - "const": "integrationTest", - "description": "Integration test task template - Generate and run integration tests for migrated Azure services. Only include when user explicitly requests integration testing. This task runs after all transform/upgrade tasks but before containerization." - }, - "layers": { - "type": "array", - "description": "[Array of test layers to execute, e.g., [1, 2] for Layer 1 (Local Integration with TestContainers) and Layer 2 (Smoke Tests)]", - "items": { - "type": "number", - "enum": [1, 2] - }, - "minItems": 1 - } - } - } - ] - }, - "securityTask": { - "allOf": [ - { "$ref": "#/$defs/taskBase" }, - { - "type": "object", - "additionalProperties": false, - "required": ["type", "successCriteria"], - "properties": { - "type": { - "const": "security", - "description": "Security task template - Include when security remediation, vulnerability scanning, or compliance checks are required." - }, - "cveReport": { - "type": ["string"], - "description": "Path to final-cve-report.json. Only has value after CVE checking is complete." - }, - "successCriteria": { "$ref": "#/$defs/successCriteria" }, - "successCriteriaStatus": { "$ref": "#/$defs/successCriteriaStatus" } - } - } - ] - }, - "infrastructureTask": { - "allOf": [ - { "$ref": "#/$defs/taskBase" }, - { - "type": "object", - "additionalProperties": false, - "required": ["type", "skills", "iacType", "provision"], - "properties": { - "type": { - "const": "infrastructure", - "description": "Infrastructure task template - Generate IaC files (Bicep or Terraform) to provision Azure resources." - }, - "iacType": { - "type": "string", - "description": "Infrastructure as Code tool type.", - "enum": ["bicep", "terraform"] - }, - "provision": { - "type": "boolean", - "description": "Whether to provision Azure resources after generating IaC files." - }, - "skills": { - "type": "array", - "description": "The skill used for this task, e.g., 'infrastructure-bicep-generation' or 'infrastructure-terraform-generation'", - "items": { "$ref": "#/$defs/skill" } - } - } - } - ] - } - } -} diff --git a/.github/skills/cve-known-vulnerabilities/SKILL.md b/.github/skills/cve-known-vulnerabilities/SKILL.md deleted file mode 100644 index 2ab39f825..000000000 --- a/.github/skills/cve-known-vulnerabilities/SKILL.md +++ /dev/null @@ -1,201 +0,0 @@ ---- -name: cve-known-vulnerabilities -description: Detect known CVE vulnerabilities in Java project dependencies using the GitHub Security Advisories API ---- - -# CVE Security Assessment: Known Dependency Vulnerabilities - -## Role - -You are a **dependency security scanner**. Your task is to programmatically collect the project's Java dependencies, query the GitHub Security Advisories API for known CVEs, and report findings in a structured JSON format. - -> **Important:** You are an auditor, NOT an implementation developer. Your sole responsibility is to identify whether known CVE vulnerabilities exist in the project's dependencies. You may recommend dependency upgrades to patched versions, but do NOT make code changes or modify build files yourself. - -## Objective - -Identify all known CVE vulnerabilities in the project's direct and transitive dependencies by: -1. Detecting the build tool (Maven or Gradle) -2. Collecting dependency coordinates (`groupId:artifactId:version`) -3. Querying the GitHub Security Advisories API -4. Reporting findings in structured JSON format - -## Instructions - -### Step 1: Detect Build Tool - -Check which build tool the project uses: - -```bash -if [ -f "pom.xml" ]; then - BUILD_TOOL="maven" -elif [ -f "build.gradle" ] || [ -f "build.gradle.kts" ]; then - BUILD_TOOL="gradle" -else - echo "No supported Java build tool found" - mkdir -p security - printf '[]\n' > security/cve-assessment-result.json - exit 0 -fi -``` - -### Step 2: Collect Dependencies - -#### Maven Projects - -Try the CLI approach first (produces the most accurate results): - -```bash -# Detect wrapper vs bare command -if [ -f "./mvnw" ]; then MVN_CMD="./mvnw"; elif [ -f "./mvnw.cmd" ]; then MVN_CMD="./mvnw.cmd"; else MVN_CMD="mvn"; fi - -# Collect all dependencies as a tree (direct + transitive with parent chain) -$MVN_CMD dependency:tree -DoutputFile=/tmp/mvn-deps.txt -DappendOutput=true -B -q 2>/dev/null -``` - -Parse the output file. The tree format uses indentation to show the dependency hierarchy: -- The root line is the project itself (depth 0) — skip it. -- Depth-1 entries (prefixed with `+- ` or `\- `) are **direct** dependencies. -- Depth-2+ entries are **transitive** dependencies, nested under the direct dependency that pulls them in. -- Each entry has the format: `groupId:artifactId:packaging:version:scope` (5 parts) or `groupId:artifactId:packaging:classifier:version:scope` (6 parts). Extract `groupId:artifactId:version` from each. - -For transitive dependencies (depth 2+), trace them back to their depth-1 ancestor (the direct dependency that pulls them in), and use that direct dependency's declaration line number in the evidence. - -**Fallback** — if the CLI command fails or is unavailable, parse `pom.xml` files directly (direct dependencies only): -- Find all `pom.xml` files (excluding `target/`, `node_modules/`, `.git/`) -- Extract `` elements: ``, ``, `` -- Resolve `${property}` references from the `` section of the same POM -- Skip dependencies where the version cannot be resolved - -#### Gradle Projects - -Try the CLI approach first: - -```bash -# Detect wrapper vs bare command -if [ -f "./gradlew" ]; then GRADLE_CMD="./gradlew"; elif [ -f "./gradlew.bat" ]; then GRADLE_CMD="./gradlew.bat"; else GRADLE_CMD="gradle"; fi - -# In multi-project builds, Gradle automatically runs the task on all subprojects -$GRADLE_CMD dependencies --configuration runtimeClasspath -q 2>/dev/null -``` - -Parse the dependency tree output. The tree format uses indentation to show the dependency hierarchy: -- Depth-1 entries (top-level `+---` or `\---`) are **direct** dependencies. -- Depth-2+ entries (nested under a direct dependency) are **transitive** dependencies. - -For each dependency line: -- Only process lines containing `---` (tree markers like `+---` or `\---`) -- Strip tree formatting characters (`+---`, `\---`, `|`, leading whitespace) -- Handle version conflict resolution: `group:artifact:1.0 -> 2.0` means use version `2.0` -- Skip `project :submodule` entries and entries with `FAILED` versions -- Remove `(*)` duplicate markers and `(c)` constraint markers -- Extract `groupId:artifactId:version` - -For transitive dependencies (depth 2+), trace them back to their depth-1 ancestor (the direct dependency that pulls them in), and use that direct dependency's declaration line number in the evidence. - -**Fallback** — if the CLI command fails, parse `build.gradle` / `build.gradle.kts` files (direct dependencies only): -- Look for dependency declarations: `implementation`, `api`, `compileOnly`, `runtimeOnly`, etc. -- Extract GAV from patterns like `implementation 'group:artifact:version'` or `implementation("group:artifact:version")` -- Skip entries where the version contains `$` (unresolved properties) - -### Step 3: Query GitHub Security Advisories API - -Resolve the GitHub token and query the API: - -```bash -# Resolve token -TOKEN="${GITHUB_TOKEN:-$(gh auth token 2>/dev/null)}" -``` - -**IMPORTANT: Batch dependencies in groups of 30** to avoid URL length limits. - -For each batch, construct the `affects` parameter and query: - -```bash -# Build the affects parameter: groupId:artifactId@version,groupId:artifactId@version,... -# URL-encode the affects value - -# Query using gh api (preferred — avoids firewall issues): -gh api "/advisories?ecosystem=maven&affects=${ENCODED_AFFECTS}&per_page=100" \ - --header "X-GitHub-Api-Version: 2022-11-28" \ - --paginate -``` - -If `gh api` is not available, fall back to `curl`. Only include the `Authorization` header when a token is available; omit it for unauthenticated requests: - -```bash -AUTH_HEADER="" -if [ -n "$TOKEN" ]; then - AUTH_HEADER="-H \"Authorization: Bearer $TOKEN\"" -fi - -eval curl -s -H "Accept: application/vnd.github+json" \ - $AUTH_HEADER \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "https://api.github.com/advisories?ecosystem=maven&affects=${ENCODED_AFFECTS}&per_page=100" -``` - -**Handle pagination**: If using `curl`, check the `Link` response header for `rel="next"` and follow it until there are no more pages. - -### Step 4: Process API Results - -For each advisory returned: -1. **Skip** advisories where `withdrawn_at` is not null (withdrawn advisories) -2. **Extract** the CVE ID from `cve_id` (fall back to `ghsa_id` if `cve_id` is null) -3. **Extract** severity from the `severity` field -4. **Extract** `summary`, `description`, and `html_url` -5. **Extract** affected package details from `vulnerabilities[]`: `package.name`, `vulnerable_version_range`, `first_patched_version` - -### Step 5: Format and Locate Evidence - -For each CVE found: -1. Identify which build files (`pom.xml`, `build.gradle`, `build.gradle.kts`) declare the affected dependency -2. Find the line number where the dependency is declared (if possible) -3. Build the evidence with file paths (e.g., `pom.xml:42`) and a detailed explanation including: - - Link to the CVE advisory - - Severity level - - List of affected dependencies with their declaration locations - - Recommended fix (upgrade to patched version if available) - -## Output Format - -Write the result as a **flat JSON array** (NOT wrapped in a result envelope) to the output file. - -If **no vulnerabilities are found**, write an empty array: `[]` - -If **vulnerabilities are found**, write an array of finding objects: - -```json -[ - { - "id": "CVE-2024-22233", - "name": "Spring Framework URL parsing vulnerability", - "status": "FOUND", - "category": "CVE", - "severity": "high", - "storyPoint": 1, - "evidence": { - "files": ["pom.xml:42", "module-b/pom.xml:18"], - "explanation": "[CVE-2024-22233](https://github.com/advisories/GHSA-xxxx): Spring Framework URL parsing vulnerability\n\nSeverity: HIGH\n\nAffected dependencies:\n - org.springframework:spring-core:5.3.20 (declared at pom.xml:42)\n - io.netty:netty-codec-http:4.1.86 (transitive, pulled by spring-boot-starter-web at pom.xml:42)\n\nRecommended fix:\n - Upgrade org.springframework:spring-core to 5.3.27 or later" - } - } -] -``` - -### Field Requirements - -- **id**: The CVE identifier (e.g., `CVE-2024-22233`), or GHSA ID if no CVE ID exists -- **name**: The advisory summary text -- **status**: Always `"FOUND"` for reported vulnerabilities -- **category**: Always `"CVE"` -- **severity**: The raw GitHub Advisory severity: `"critical"`, `"high"`, `"medium"`, or `"low"` -- **storyPoint**: Always `1` -- **evidence.files**: Array of workspace-relative file paths where affected dependencies are declared (with optional `:lineNumber` suffix). For transitive dependencies, use the line number of the direct dependency that pulls them in (e.g., if `netty-codec-http` is pulled in by `spring-boot-starter-web` declared at `pom.xml:42`, use `pom.xml:42`) -- **evidence.explanation**: Markdown-formatted description including CVE link, severity, affected dependencies with locations, and recommended fix - -### Important Notes - -- If a single CVE affects multiple dependencies, group them into ONE finding with all affected dependencies listed in the explanation -- If no build tool is detected, write `[]` and skip the assessment -- If the GitHub token is unavailable, proceed anyway (unauthenticated rate limits apply) -- If the API query fails, write `[]` and log the error -- Deduplicate: if the same CVE appears across multiple API batches, merge into a single finding diff --git a/.github/skills/cwe-code-quality/SKILL.md b/.github/skills/cwe-code-quality/SKILL.md deleted file mode 100644 index 9785ba5a3..000000000 --- a/.github/skills/cwe-code-quality/SKILL.md +++ /dev/null @@ -1,153 +0,0 @@ ---- -name: cwe-code-quality -description: Assess codebase for CWE code quality vulnerabilities (CWE-130, CWE-456, CWE-457, CWE-477, CWE-570, CWE-571, CWE-606, CWE-665, CWE-681, CWE-682, CWE-772, CWE-775, CWE-783, CWE-789, CWE-835, CWE-1057) ---- - -# CWE Security Assessment: Code Quality - -## Role - -You are an expert **code security reviewer** specializing in CWE vulnerability detection. - -> **Important:** You are an auditor, NOT an implementation developer. Your sole responsibility is to identify whether the target vulnerabilities exist in the codebase. Do NOT suggest fixes or improvements. - -## Objective - -Analyze the application codebase for each of the 16 CWE rules listed below. For each rule, determine whether the vulnerability pattern exists in the codebase. - -## CWE Rules to Assess - -### CWE-130: Improper Handling of Length Parameter Inconsistency -- **Severity:** potential | **Story Points:** 3 -- **Description:** The product parses a formatted message or structure, but it does not handle or incorrectly handles a length field that is inconsistent with the actual length of the associated data. - -### CWE-456: Missing Initialization of a Variable -- **Severity:** potential | **Story Points:** 2 -- **Description:** The product does not initialize critical variables, which causes the execution environment to use unexpected values. - -### CWE-457: Use of Uninitialized Variable -- **Severity:** potential | **Story Points:** 2 -- **Description:** The code uses a variable that has not been initialized, leading to unpredictable or unintended results. - -### CWE-477: Use of Obsolete Function -- **Severity:** optional | **Story Points:** 1 -- **Description:** The code uses deprecated or obsolete functions, which suggests that the code has not been actively reviewed or maintained. - -### CWE-570: Expression is Always False -- **Severity:** optional | **Story Points:** 1 -- **Description:** The product contains an expression that will always evaluate to false. - -### CWE-571: Expression is Always True -- **Severity:** optional | **Story Points:** 1 -- **Description:** The product contains an expression that will always evaluate to true. - -### CWE-606: Unchecked Input for Loop Condition -- **Severity:** potential | **Story Points:** 3 -- **Description:** The product does not properly check inputs that are used for loop conditions, potentially leading to a denial of service or other consequences because of excessive looping. - -### CWE-665: Improper Initialization -- **Severity:** potential | **Story Points:** 3 -- **Description:** The product does not initialize or incorrectly initializes a resource, which might leave the resource in an unexpected state when it is accessed or used. - -### CWE-681: Incorrect Conversion between Numeric Types -- **Severity:** potential | **Story Points:** 3 -- **Description:** When converting from one data type to another, such as long to integer, data can be omitted or translated in a way that produces unexpected values. If the resulting values are used in a sensitive context, then dangerous behaviors may occur. - -### CWE-682: Incorrect Calculation -- **Severity:** potential | **Story Points:** 5 -- **Description:** The product performs a calculation that generates incorrect or unintended results that are later used in security-critical decisions or resource management. - -### CWE-772: Missing Release of Resource after Effective Lifetime -- **Severity:** potential | **Story Points:** 3 -- **Description:** The product does not release a resource after its effective lifetime has ended, i.e., after the resource is no longer needed. - -### CWE-775: Missing Release of File Descriptor or Handle after Effective Lifetime -- **Severity:** potential | **Story Points:** 3 -- **Description:** The product does not release a file descriptor or handle after its effective lifetime has ended, i.e., after the file descriptor/handle is no longer needed. - -### CWE-783: Operator Precedence Logic Error -- **Severity:** optional | **Story Points:** 1 -- **Description:** The product uses an expression in which operator precedence causes incorrect logic to be used. - -### CWE-789: Memory Allocation with Excessive Size Value -- **Severity:** potential | **Story Points:** 5 -- **Description:** The product allocates memory based on an untrusted, large size value, but it does not ensure that the size is within expected limits, allowing arbitrary amounts of memory to be allocated. - -### CWE-835: Loop with Unreachable Exit Condition ('Infinite Loop') -- **Severity:** potential | **Story Points:** 3 -- **Description:** The product contains an iteration or loop with an exit condition that cannot be reached, i.e., an infinite loop. - -### CWE-1057: Data Access Operations Outside of Expected Data Manager Component -- **Severity:** potential | **Story Points:** 5 -- **Description:** The product uses a dedicated, central data manager component as required by design, but it contains code that performs data-access operations that do not use this data manager. - -## Instructions - -1. **Iterate through each CWE rule** listed above -2. **Systematically scan** the application source code for patterns matching each rule -3. **For each rule:** Stop scanning as soon as you find the FIRST confirmed match -4. **Continue to the next rule** after finding a match or exhausting the search -5. **Report findings** for ALL rules (both FOUND and NOT_FOUND) - -### Search Strategy - -- Start with common vulnerability patterns: user input handling, external data processing, resource management -- Check configuration files, API endpoints, data access layers, and utility classes -- Consider both direct patterns and indirect/transitive vulnerability paths -- Focus on source files (e.g., `.java`, `.py`, `.cs`, `.js`, `.ts`) — skip test files and generated code - -## Output Format - -Use the `write_assessment_result` tool to save results with the following JSON structure: - -```json -{ - "input_name": "CWE - Code Quality", - "analysis_method": "LLM", - "status": "success", - "result": { - "finding": "Assessed 16 CWE rules in Code Quality: X FOUND, Y NOT_FOUND", - "confidence": "high", - "evidence": ["Scanned application source files for code quality patterns"], - "values": [ - { - "id": "", - "name": "", - "status": "FOUND", - "category": "Code Quality", - "severity": "", - "storyPoint": "", - "description": "", - "evidence": { - "files": ["src/path/to/File.java"], - "explanation": "Description of the vulnerability found, including class/method and line reference" - } - }, - { - "id": "", - "name": "", - "status": "NOT_FOUND", - "category": "Code Quality", - "severity": "", - "storyPoint": "", - "description": "", - "evidence": { - "files": [], - "explanation": "" - } - } - ] - }, - "execution_time_seconds": 0, - "timestamp": "" -} -``` - -### Evidence Rules - -- **FOUND**: `files` must contain workspace-relative file paths. `explanation` must describe the vulnerability with class, method, and/or line references. -- **NOT_FOUND**: `files` must be an empty array `[]`. `explanation` must be an empty string `""`. -- **Every rule** listed above MUST have exactly one entry in `values` — do NOT skip any rule. -- **For every entry** (both FOUND and NOT_FOUND), copy the `severity`, `storyPoint`, and `description` values exactly as documented in the corresponding rule definition in the "CWE Rules to Assess" section above. -- Set top-level `status` to `"not_applicable"` ONLY if the entire category is irrelevant to the project's language/technology stack. -- Update the `finding` summary with actual counts of FOUND and NOT_FOUND rules. diff --git a/.github/skills/cwe-concurrency-synchronization/SKILL.md b/.github/skills/cwe-concurrency-synchronization/SKILL.md deleted file mode 100644 index 7ff74ae9e..000000000 --- a/.github/skills/cwe-concurrency-synchronization/SKILL.md +++ /dev/null @@ -1,113 +0,0 @@ ---- -name: cwe-concurrency-synchronization -description: Assess codebase for CWE concurrency & synchronization vulnerabilities (CWE-543, CWE-567, CWE-662, CWE-667, CWE-820, CWE-821) ---- - -# CWE Security Assessment: Concurrency & Synchronization - -## Role - -You are an expert **code security reviewer** specializing in CWE vulnerability detection. - -> **Important:** You are an auditor, NOT an implementation developer. Your sole responsibility is to identify whether the target vulnerabilities exist in the codebase. Do NOT suggest fixes or improvements. - -## Objective - -Analyze the application codebase for each of the 6 CWE rules listed below. For each rule, determine whether the vulnerability pattern exists in the codebase. - -## CWE Rules to Assess - -### CWE-543: Use of Singleton Pattern Without Synchronization in a Multithreaded Context -- **Severity:** potential | **Story Points:** 5 -- **Description:** The product uses the singleton pattern when creating a resource within a multithreaded environment. - -### CWE-567: Unsynchronized Access to Shared Data in a Multithreaded Context -- **Severity:** potential | **Story Points:** 5 -- **Description:** The product does not properly synchronize shared data, such as static variables across threads, which can lead to undefined behavior and unpredictable data changes. - -### CWE-662: Improper Synchronization -- **Severity:** potential | **Story Points:** 8 -- **Description:** The product utilizes multiple threads or processes to allow temporary access to a shared resource that can only be exclusive to one process at a time, but it does not properly synchronize these actions, which might cause simultaneous accesses of this resource by multiple threads or processes. - -### CWE-667: Improper Locking -- **Severity:** potential | **Story Points:** 8 -- **Description:** The product does not properly acquire or release a lock on a resource, leading to unexpected resource state changes and behaviors. - -### CWE-820: Missing Synchronization -- **Severity:** potential | **Story Points:** 8 -- **Description:** The product utilizes a shared resource in a concurrent manner but does not attempt to synchronize access to the resource. - -### CWE-821: Incorrect Synchronization -- **Severity:** potential | **Story Points:** 8 -- **Description:** The product utilizes a shared resource in a concurrent manner, but it does not correctly synchronize access to the resource. - -## Instructions - -1. **Iterate through each CWE rule** listed above -2. **Systematically scan** the application source code for patterns matching each rule -3. **For each rule:** Stop scanning as soon as you find the FIRST confirmed match -4. **Continue to the next rule** after finding a match or exhausting the search -5. **Report findings** for ALL rules (both FOUND and NOT_FOUND) - -### Search Strategy - -- Start with common vulnerability patterns: user input handling, external data processing, resource management -- Check configuration files, API endpoints, data access layers, and utility classes -- Consider both direct patterns and indirect/transitive vulnerability paths -- Focus on source files (e.g., `.java`, `.py`, `.cs`, `.js`, `.ts`) — skip test files and generated code - -## Output Format - -Use the `write_assessment_result` tool to save results with the following JSON structure: - -```json -{ - "input_name": "CWE - Concurrency & Synchronization", - "analysis_method": "LLM", - "status": "success", - "result": { - "finding": "Assessed 6 CWE rules in Concurrency & Synchronization: X FOUND, Y NOT_FOUND", - "confidence": "high", - "evidence": ["Scanned application source files for concurrency & synchronization patterns"], - "values": [ - { - "id": "", - "name": "", - "status": "FOUND", - "category": "Concurrency & Synchronization", - "severity": "", - "storyPoint": "", - "description": "", - "evidence": { - "files": ["src/path/to/File.java"], - "explanation": "Description of the vulnerability found, including class/method and line reference" - } - }, - { - "id": "", - "name": "", - "status": "NOT_FOUND", - "category": "Concurrency & Synchronization", - "severity": "", - "storyPoint": "", - "description": "", - "evidence": { - "files": [], - "explanation": "" - } - } - ] - }, - "execution_time_seconds": 0, - "timestamp": "" -} -``` - -### Evidence Rules - -- **FOUND**: `files` must contain workspace-relative file paths. `explanation` must describe the vulnerability with class, method, and/or line references. -- **NOT_FOUND**: `files` must be an empty array `[]`. `explanation` must be an empty string `""`. -- **Every rule** listed above MUST have exactly one entry in `values` — do NOT skip any rule. -- **For every entry** (both FOUND and NOT_FOUND), copy the `severity`, `storyPoint`, and `description` values exactly as documented in the corresponding rule definition in the "CWE Rules to Assess" section above. -- Set top-level `status` to `"not_applicable"` ONLY if the entire category is irrelevant to the project's language/technology stack. -- Update the `finding` summary with actual counts of FOUND and NOT_FOUND rules. diff --git a/.github/skills/cwe-credentials-secrets/SKILL.md b/.github/skills/cwe-credentials-secrets/SKILL.md deleted file mode 100644 index fe1e29b6f..000000000 --- a/.github/skills/cwe-credentials-secrets/SKILL.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -name: cwe-credentials-secrets -description: Assess codebase for CWE credentials & secrets vulnerabilities (CWE-259, CWE-321, CWE-732, CWE-778, CWE-798) ---- - -# CWE Security Assessment: Credentials & Secrets - -## Role - -You are an expert **code security reviewer** specializing in CWE vulnerability detection. - -> **Important:** You are an auditor, NOT an implementation developer. Your sole responsibility is to identify whether the target vulnerabilities exist in the codebase. Do NOT suggest fixes or improvements. - -## Objective - -Analyze the application codebase for each of the 5 CWE rules listed below. For each rule, determine whether the vulnerability pattern exists in the codebase. - -## CWE Rules to Assess - -### CWE-259: Use of Hard-coded Password -- **Severity:** optional | **Story Points:** 5 -- **Description:** The product contains a hard-coded password, which it uses for its own inbound authentication or for outbound communication to external components. - -### CWE-321: Use of Hard-coded Cryptographic Key -- **Severity:** potential | **Story Points:** 5 -- **Description:** The product uses a hard-coded, unchangeable cryptographic key. - -### CWE-732: Incorrect Permission Assignment for Critical Resource -- **Severity:** optional | **Story Points:** 5 -- **Description:** The product specifies permissions for a security-critical resource in a way that allows that resource to be read or modified by unintended actors. - -### CWE-778: Insufficient Logging -- **Severity:** potential | **Story Points:** 3 -- **Description:** When a security-critical event occurs, the product either does not record the event or omits important details about the event when logging it. - -### CWE-798: Use of Hard-coded Credentials -- **Severity:** optional | **Story Points:** 5 -- **Description:** The product contains hard-coded credentials, such as a password or cryptographic key. - -## Instructions - -1. **Iterate through each CWE rule** listed above -2. **Systematically scan** the application source code for patterns matching each rule -3. **For each rule:** Stop scanning as soon as you find the FIRST confirmed match -4. **Continue to the next rule** after finding a match or exhausting the search -5. **Report findings** for ALL rules (both FOUND and NOT_FOUND) - -### Search Strategy - -- Start with common vulnerability patterns: user input handling, external data processing, resource management -- Check configuration files, API endpoints, data access layers, and utility classes -- Consider both direct patterns and indirect/transitive vulnerability paths -- Focus on source files (e.g., `.java`, `.py`, `.cs`, `.js`, `.ts`) — skip test files and generated code - -## Output Format - -Use the `write_assessment_result` tool to save results with the following JSON structure: - -```json -{ - "input_name": "CWE - Credentials & Secrets", - "analysis_method": "LLM", - "status": "success", - "result": { - "finding": "Assessed 5 CWE rules in Credentials & Secrets: X FOUND, Y NOT_FOUND", - "confidence": "high", - "evidence": ["Scanned application source files for credentials & secrets patterns"], - "values": [ - { - "id": "", - "name": "", - "status": "FOUND", - "category": "Credentials & Secrets", - "severity": "", - "storyPoint": "", - "description": "", - "evidence": { - "files": ["src/path/to/File.java"], - "explanation": "Description of the vulnerability found, including class/method and line reference" - } - }, - { - "id": "", - "name": "", - "status": "NOT_FOUND", - "category": "Credentials & Secrets", - "severity": "", - "storyPoint": "", - "description": "", - "evidence": { - "files": [], - "explanation": "" - } - } - ] - }, - "execution_time_seconds": 0, - "timestamp": "" -} -``` - -### Evidence Rules - -- **FOUND**: `files` must contain workspace-relative file paths. `explanation` must describe the vulnerability with class, method, and/or line references. -- **NOT_FOUND**: `files` must be an empty array `[]`. `explanation` must be an empty string `""`. -- **Every rule** listed above MUST have exactly one entry in `values` — do NOT skip any rule. -- **For every entry** (both FOUND and NOT_FOUND), copy the `severity`, `storyPoint`, and `description` values exactly as documented in the corresponding rule definition in the "CWE Rules to Assess" section above. -- Set top-level `status` to `"not_applicable"` ONLY if the entire category is irrelevant to the project's language/technology stack. -- Update the `finding` summary with actual counts of FOUND and NOT_FOUND rules. diff --git a/.github/skills/cwe-file-path-security/SKILL.md b/.github/skills/cwe-file-path-security/SKILL.md deleted file mode 100644 index 1cac17b05..000000000 --- a/.github/skills/cwe-file-path-security/SKILL.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -name: cwe-file-path-security -description: Assess codebase for CWE file & path security vulnerabilities (CWE-22, CWE-23, CWE-36, CWE-434, CWE-611) ---- - -# CWE Security Assessment: File & Path Security - -## Role - -You are an expert **code security reviewer** specializing in CWE vulnerability detection. - -> **Important:** You are an auditor, NOT an implementation developer. Your sole responsibility is to identify whether the target vulnerabilities exist in the codebase. Do NOT suggest fixes or improvements. - -## Objective - -Analyze the application codebase for each of the 5 CWE rules listed below. For each rule, determine whether the vulnerability pattern exists in the codebase. - -## CWE Rules to Assess - -### CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') -- **Severity:** optional | **Story Points:** 8 -- **Description:** The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory. - -### CWE-23: Relative Path Traversal -- **Severity:** optional | **Story Points:** 5 -- **Description:** The product uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize sequences such as .. that can resolve to a location that is outside of that directory. - -### CWE-36: Absolute Path Traversal -- **Severity:** optional | **Story Points:** 5 -- **Description:** The product uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize absolute path sequences such as /abs/path that can resolve to a location that is outside of that directory. - -### CWE-434: Unrestricted Upload of File with Dangerous Type -- **Severity:** mandatory | **Story Points:** 8 -- **Description:** The product allows the upload or transfer of dangerous file types that are automatically processed within its environment. - -### CWE-611: Improper Restriction of XML External Entity Reference -- **Severity:** optional | **Story Points:** 5 -- **Description:** The product processes an XML document that can contain XML entities with URIs that resolve to documents outside of the intended sphere of control, causing the product to embed incorrect documents into its output. - -## Instructions - -1. **Iterate through each CWE rule** listed above -2. **Systematically scan** the application source code for patterns matching each rule -3. **For each rule:** Stop scanning as soon as you find the FIRST confirmed match -4. **Continue to the next rule** after finding a match or exhausting the search -5. **Report findings** for ALL rules (both FOUND and NOT_FOUND) - -### Search Strategy - -- Start with common vulnerability patterns: user input handling, external data processing, resource management -- Check configuration files, API endpoints, data access layers, and utility classes -- Consider both direct patterns and indirect/transitive vulnerability paths -- Focus on source files (e.g., `.java`, `.py`, `.cs`, `.js`, `.ts`) — skip test files and generated code - -## Output Format - -Use the `write_assessment_result` tool to save results with the following JSON structure: - -```json -{ - "input_name": "CWE - File & Path Security", - "analysis_method": "LLM", - "status": "success", - "result": { - "finding": "Assessed 5 CWE rules in File & Path Security: X FOUND, Y NOT_FOUND", - "confidence": "high", - "evidence": ["Scanned application source files for file & path security patterns"], - "values": [ - { - "id": "", - "name": "", - "status": "FOUND", - "category": "File & Path Security", - "severity": "", - "storyPoint": "", - "description": "", - "evidence": { - "files": ["src/path/to/File.java"], - "explanation": "Description of the vulnerability found, including class/method and line reference" - } - }, - { - "id": "", - "name": "", - "status": "NOT_FOUND", - "category": "File & Path Security", - "severity": "", - "storyPoint": "", - "description": "", - "evidence": { - "files": [], - "explanation": "" - } - } - ] - }, - "execution_time_seconds": 0, - "timestamp": "" -} -``` - -### Evidence Rules - -- **FOUND**: `files` must contain workspace-relative file paths. `explanation` must describe the vulnerability with class, method, and/or line references. -- **NOT_FOUND**: `files` must be an empty array `[]`. `explanation` must be an empty string `""`. -- **Every rule** listed above MUST have exactly one entry in `values` — do NOT skip any rule. -- **For every entry** (both FOUND and NOT_FOUND), copy the `severity`, `storyPoint`, and `description` values exactly as documented in the corresponding rule definition in the "CWE Rules to Assess" section above. -- Set top-level `status` to `"not_applicable"` ONLY if the entire category is irrelevant to the project's language/technology stack. -- Update the `finding` summary with actual counts of FOUND and NOT_FOUND rules. diff --git a/.github/skills/cwe-injection-attacks/SKILL.md b/.github/skills/cwe-injection-attacks/SKILL.md deleted file mode 100644 index 347105abb..000000000 --- a/.github/skills/cwe-injection-attacks/SKILL.md +++ /dev/null @@ -1,137 +0,0 @@ ---- -name: cwe-injection-attacks -description: Assess codebase for CWE injection attacks vulnerabilities (CWE-77, CWE-78, CWE-79, CWE-88, CWE-89, CWE-90, CWE-91, CWE-99, CWE-502, CWE-564, CWE-643, CWE-652) ---- - -# CWE Security Assessment: Injection Attacks - -## Role - -You are an expert **code security reviewer** specializing in CWE vulnerability detection. - -> **Important:** You are an auditor, NOT an implementation developer. Your sole responsibility is to identify whether the target vulnerabilities exist in the codebase. Do NOT suggest fixes or improvements. - -## Objective - -Analyze the application codebase for each of the 12 CWE rules listed below. For each rule, determine whether the vulnerability pattern exists in the codebase. - -## CWE Rules to Assess - -### CWE-77: Improper Neutralization of Special Elements used in a Command ('Command Injection') -- **Severity:** mandatory | **Story Points:** 13 -- **Description:** The product constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component. - -### CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') -- **Severity:** mandatory | **Story Points:** 13 -- **Description:** The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component. - -### CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') -- **Severity:** optional | **Story Points:** 8 -- **Description:** The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users. - -### CWE-88: Improper Neutralization of Argument Delimiters in a Command ('Argument Injection') -- **Severity:** optional | **Story Points:** 5 -- **Description:** The product constructs a string for a command to be executed by a separate component in another control sphere, but it does not properly delimit the intended arguments, options, or switches within that command string. - -### CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') -- **Severity:** mandatory | **Story Points:** 13 -- **Description:** The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data. - -### CWE-90: Improper Neutralization of Special Elements used in an LDAP Query ('LDAP Injection') -- **Severity:** optional | **Story Points:** 5 -- **Description:** The product constructs all or part of an LDAP query using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended LDAP query when it is sent to a downstream component. - -### CWE-91: XML Injection (aka Blind XPath Injection) -- **Severity:** optional | **Story Points:** 5 -- **Description:** The product does not properly neutralize special elements that are used in XML, allowing attackers to modify the syntax, content, or commands of the XML before it is processed by an end system. - -### CWE-99: Improper Control of Resource Identifiers ('Resource Injection') -- **Severity:** potential | **Story Points:** 3 -- **Description:** The product receives input from an upstream component, but it does not restrict or incorrectly restricts the input before it is used as an identifier for a resource that may be outside the intended sphere of control. - -### CWE-502: Deserialization of Untrusted Data -- **Severity:** mandatory | **Story Points:** 13 -- **Description:** The product deserializes untrusted data without sufficiently ensuring that the resulting data will be valid. - -### CWE-564: SQL Injection: Hibernate -- **Severity:** mandatory | **Story Points:** 8 -- **Description:** Using Hibernate to execute a dynamic SQL statement built with user-controlled input can allow an attacker to modify the statement's meaning or to execute arbitrary SQL commands. - -### CWE-643: Improper Neutralization of Data within XPath Expressions ('XPath Injection') -- **Severity:** optional | **Story Points:** 5 -- **Description:** The product uses external input to dynamically construct an XPath expression used to retrieve data from an XML database, but it does not neutralize or incorrectly neutralizes that input. This allows an attacker to control the structure of the query. - -### CWE-652: Improper Neutralization of Data within XQuery Expressions ('XQuery Injection') -- **Severity:** optional | **Story Points:** 5 -- **Description:** The product uses external input to dynamically construct an XQuery expression used to retrieve data from an XML database, but it does not neutralize or incorrectly neutralizes that input. This allows an attacker to control the structure of the query. - -## Instructions - -1. **Iterate through each CWE rule** listed above -2. **Systematically scan** the application source code for patterns matching each rule -3. **For each rule:** Stop scanning as soon as you find the FIRST confirmed match -4. **Continue to the next rule** after finding a match or exhausting the search -5. **Report findings** for ALL rules (both FOUND and NOT_FOUND) - -### Search Strategy - -- Start with common vulnerability patterns: user input handling, external data processing, resource management -- Check configuration files, API endpoints, data access layers, and utility classes -- Consider both direct patterns and indirect/transitive vulnerability paths -- Focus on source files (e.g., `.java`, `.py`, `.cs`, `.js`, `.ts`) — skip test files and generated code - -## Output Format - -Use the `write_assessment_result` tool to save results with the following JSON structure: - -```json -{ - "input_name": "CWE - Injection Attacks", - "analysis_method": "LLM", - "status": "success", - "result": { - "finding": "Assessed 12 CWE rules in Injection Attacks: X FOUND, Y NOT_FOUND", - "confidence": "high", - "evidence": ["Scanned application source files for injection attacks patterns"], - "values": [ - { - "id": "", - "name": "", - "status": "FOUND", - "category": "Injection Attacks", - "severity": "", - "storyPoint": "", - "description": "", - "evidence": { - "files": ["src/path/to/File.java"], - "explanation": "Description of the vulnerability found, including class/method and line reference" - } - }, - { - "id": "", - "name": "", - "status": "NOT_FOUND", - "category": "Injection Attacks", - "severity": "", - "storyPoint": "", - "description": "", - "evidence": { - "files": [], - "explanation": "" - } - } - ] - }, - "execution_time_seconds": 0, - "timestamp": "" -} -``` - -### Evidence Rules - -- **FOUND**: `files` must contain workspace-relative file paths. `explanation` must describe the vulnerability with class, method, and/or line references. -- **NOT_FOUND**: `files` must be an empty array `[]`. `explanation` must be an empty string `""`. -- **Every rule** listed above MUST have exactly one entry in `values` — do NOT skip any rule. -- **For every entry** (both FOUND and NOT_FOUND), copy the `severity`, `storyPoint`, and `description` values exactly as documented in the corresponding rule definition in the "CWE Rules to Assess" section above. -- Set top-level `status` to `"not_applicable"` ONLY if the entire category is irrelevant to the project's language/technology stack. -- Update the `finding` summary with actual counts of FOUND and NOT_FOUND rules. diff --git a/.github/skills/cwe-memory-safety/SKILL.md b/.github/skills/cwe-memory-safety/SKILL.md deleted file mode 100644 index be7ffa439..000000000 --- a/.github/skills/cwe-memory-safety/SKILL.md +++ /dev/null @@ -1,149 +0,0 @@ ---- -name: cwe-memory-safety -description: Assess codebase for CWE memory safety vulnerabilities (CWE-119, CWE-120, CWE-123, CWE-125, CWE-415, CWE-416, CWE-672, CWE-786, CWE-787, CWE-788, CWE-805, CWE-822, CWE-823, CWE-824, CWE-825) ---- - -# CWE Security Assessment: Memory Safety - -## Role - -You are an expert **code security reviewer** specializing in CWE vulnerability detection. - -> **Important:** You are an auditor, NOT an implementation developer. Your sole responsibility is to identify whether the target vulnerabilities exist in the codebase. Do NOT suggest fixes or improvements. - -## Objective - -Analyze the application codebase for each of the 15 CWE rules listed below. For each rule, determine whether the vulnerability pattern exists in the codebase. - -## CWE Rules to Assess - -### CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer -- **Severity:** mandatory | **Story Points:** 21 -- **Description:** The product performs operations on a memory buffer, but it reads from or writes to a memory location outside the buffer's intended boundary. This may result in read or write operations on unexpected memory locations that could be linked to other variables, data structures, or internal program data. - -### CWE-120: Buffer Copy without Checking Size of Input ('Classic Buffer Overflow') -- **Severity:** mandatory | **Story Points:** 13 -- **Description:** The product copies an input buffer to an output buffer without verifying that the size of the input buffer is less than the size of the output buffer. - -### CWE-123: Write-what-where Condition -- **Severity:** mandatory | **Story Points:** 13 -- **Description:** Any condition where the attacker has the ability to write an arbitrary value to an arbitrary location, often as the result of a buffer overflow. - -### CWE-125: Out-of-bounds Read -- **Severity:** optional | **Story Points:** 8 -- **Description:** The product reads data past the end, or before the beginning, of the intended buffer. - -### CWE-415: Double Free -- **Severity:** optional | **Story Points:** 8 -- **Description:** The product calls free() twice on the same memory address. - -### CWE-416: Use After Free -- **Severity:** mandatory | **Story Points:** 13 -- **Description:** The product reuses or references memory after it has been freed. At some point afterward, the memory may be allocated again and saved in another pointer, while the original pointer references a location somewhere within the new allocation. Any operations using the original pointer are no longer valid because the memory belongs to the code that operates on the new pointer. - -### CWE-672: Operation on a Resource after Expiration or Release -- **Severity:** potential | **Story Points:** 5 -- **Description:** The product uses, accesses, or otherwise operates on a resource after that resource has been expired, released, or revoked. - -### CWE-786: Access of Memory Location Before Start of Buffer -- **Severity:** optional | **Story Points:** 8 -- **Description:** The product reads or writes to a buffer using an index or pointer that references a memory location prior to the beginning of the buffer. - -### CWE-787: Out-of-bounds Write -- **Severity:** mandatory | **Story Points:** 13 -- **Description:** The product writes data past the end, or before the beginning, of the intended buffer. - -### CWE-788: Access of Memory Location After End of Buffer -- **Severity:** optional | **Story Points:** 8 -- **Description:** The product reads or writes to a buffer using an index or pointer that references a memory location after the end of the buffer. - -### CWE-805: Buffer Access with Incorrect Length Value -- **Severity:** optional | **Story Points:** 8 -- **Description:** The product uses a sequential operation to read or write a buffer, but it uses an incorrect length value that causes it to access memory that is outside of the bounds of the buffer. - -### CWE-822: Untrusted Pointer Dereference -- **Severity:** optional | **Story Points:** 8 -- **Description:** The product obtains a value from an untrusted source, converts this value to a pointer, and dereferences the resulting pointer. - -### CWE-823: Use of Out-of-range Pointer Offset -- **Severity:** optional | **Story Points:** 8 -- **Description:** The product performs pointer arithmetic on a valid pointer, but it uses an offset that can point outside of the intended range of valid memory locations for the resulting pointer. - -### CWE-824: Access of Uninitialized Pointer -- **Severity:** optional | **Story Points:** 5 -- **Description:** The product accesses or uses a pointer that has not been initialized. - -### CWE-825: Expired Pointer Dereference -- **Severity:** optional | **Story Points:** 8 -- **Description:** The product dereferences a pointer that contains a location for memory that was previously valid, but is no longer valid. - -## Instructions - -1. **Iterate through each CWE rule** listed above -2. **Systematically scan** the application source code for patterns matching each rule -3. **For each rule:** Stop scanning as soon as you find the FIRST confirmed match -4. **Continue to the next rule** after finding a match or exhausting the search -5. **Report findings** for ALL rules (both FOUND and NOT_FOUND) - -### Search Strategy - -- Start with common vulnerability patterns: user input handling, external data processing, resource management -- Check configuration files, API endpoints, data access layers, and utility classes -- Consider both direct patterns and indirect/transitive vulnerability paths -- Focus on source files (e.g., `.java`, `.py`, `.cs`, `.js`, `.ts`) — skip test files and generated code - -## Output Format - -Use the `write_assessment_result` tool to save results with the following JSON structure: - -```json -{ - "input_name": "CWE - Memory Safety", - "analysis_method": "LLM", - "status": "success", - "result": { - "finding": "Assessed 15 CWE rules in Memory Safety: X FOUND, Y NOT_FOUND", - "confidence": "high", - "evidence": ["Scanned application source files for memory safety patterns"], - "values": [ - { - "id": "", - "name": "", - "status": "FOUND", - "category": "Memory Safety", - "severity": "", - "storyPoint": "", - "description": "", - "evidence": { - "files": ["src/path/to/File.java"], - "explanation": "Description of the vulnerability found, including class/method and line reference" - } - }, - { - "id": "", - "name": "", - "status": "NOT_FOUND", - "category": "Memory Safety", - "severity": "", - "storyPoint": "", - "description": "", - "evidence": { - "files": [], - "explanation": "" - } - } - ] - }, - "execution_time_seconds": 0, - "timestamp": "" -} -``` - -### Evidence Rules - -- **FOUND**: `files` must contain workspace-relative file paths. `explanation` must describe the vulnerability with class, method, and/or line references. -- **NOT_FOUND**: `files` must be an empty array `[]`. `explanation` must be an empty string `""`. -- **Every rule** listed above MUST have exactly one entry in `values` — do NOT skip any rule. -- **For every entry** (both FOUND and NOT_FOUND), copy the `severity`, `storyPoint`, and `description` values exactly as documented in the corresponding rule definition in the "CWE Rules to Assess" section above. -- Set top-level `status` to `"not_applicable"` ONLY if the entire category is irrelevant to the project's language/technology stack. -- Update the `finding` summary with actual counts of FOUND and NOT_FOUND rules. diff --git a/.github/skills/data-architecture/SKILL.md b/.github/skills/data-architecture/SKILL.md deleted file mode 100644 index 4dd3a43d6..000000000 --- a/.github/skills/data-architecture/SKILL.md +++ /dev/null @@ -1,227 +0,0 @@ ---- -name: data-architecture -description: Generate data architecture and persistence layer documentation with data model diagram ---- - -# Data Architecture & Persistence Layer - -Analyze the project to document database configuration, entity models, data ownership boundaries, repository interfaces, and caching strategies. Generate a Mermaid ER diagram showing entity relationships. Save to `.github/modernize/assessment/engines/data-architecture.md`. - -## Input Parameters - -- `workspace-path` (optional): Path to the project to analyze (defaults to current directory) - -## Scope Boundaries — Avoid Redundancy with Other Skills - -This skill is part of a set of four complementary assessment skills. To avoid content duplication across their output documents, observe these scope rules: - -- **Introduction**: Write a 1-2 sentence intro focused on the data layer (number of entities, database types, ORM). Do NOT restate the application's overall architecture type, web framework, or API surface — those are covered by other skills. -- **Configuration property keys/values** (e.g., `spring.jpa.hibernate.ddl-auto`, `spring.sql.init.*`) are owned by the `configuration-inventory` skill. In the Database Configuration table, describe the *behavior* (e.g., "Hibernate does not manage schema; SQL scripts are authoritative") but do NOT list raw property key-value pairs. Reference `configuration-inventory.md` for the full property inventory. -- **API endpoints and HTTP methods** are owned by the `api-service-contracts` skill. Do NOT list controller endpoints or HTTP paths. Repository methods are in scope for this skill; controller routes are not. -- **Business workflow steps and validation rules** are owned by the `business-workflows` skill. Do NOT describe multi-step business processes or enumerate validation constraints. When documenting entity relationships (cascade, fetch), focus on the persistence/ORM implications, not the business process flow. -- **Deployment configurations** (Docker Compose, K8s, profiles) are owned by the `configuration-inventory` skill. Mention database profiles only in the Database Configuration table to identify which DB is used per profile — do NOT describe Docker Compose services, K8s manifests, or deployment targets in detail. - -## Execution Steps - -### Step 1: Generate Database Configuration Section - -Extract database configuration from project files, **per profile/environment**, and produce the complete `## Database Configuration` section: - -- Database types: HSQLDB, MySQL, PostgreSQL, MongoDB, SQL Server, Oracle, SQLite, CosmosDB, DynamoDB -- **Per-profile configuration**: identify which database is used in each profile (e.g., HSQLDB for `default`/dev in-memory testing, MySQL for `production`/`mysql` profile) -- Database drivers per profile (e.g., `mysql-connector-java` for production, `hsqldb` for development) -- Connection configuration: connection strings, JDBC URLs, pooling settings (HikariCP, connection pool size) -- Migration tools: Flyway, Liquibase, EF Migrations, Alembic, Prisma Migrate, Knex migrations -- Schema management: DDL auto-generation settings (`spring.jpa.hibernate.ddl-auto`), schema versioning, initial schema scripts -- Seed data: `data.sql`, `import.sql`, seed migration files, or programmatic data seeding - -### Step 2: Generate Data Ownership per Service Section - -Determine table/entity ownership across modules/services and produce the complete `## Data Ownership per Service` section. Scope is strictly the per-service ownership table — high-level data boundary discussion belongs in Step 6. - -For each module/service, identify: - -- Which tables/entities it owns (bounded context analysis) -- ORM framework used (e.g., Hibernate, EF Core, MyBatis, Mongoose) -- Caching layer used by this service (if any) -- Brief notes (e.g., outbox table, schema-per-service) - -> Do NOT include shared-vs-isolated data store summary, cross-service data access patterns, or read/write/CQRS observations here — those belong in the `## Data Ownership Boundaries` section (Step 6). - -### Step 3: Generate Entity Model Section - -Scan source code for data access patterns and ORM entities, then produce the complete `## Entity Model` section: - -**Analysis:** -- Java: JPA/Hibernate entities (`@Entity`, `@Table`), Spring Data repositories (`JpaRepository`, `CrudRepository`), MyBatis mappers, JDBC templates -- .NET: EF Core `DbContext`, EF Core entities, Dapper, ADO.NET -- JavaScript/TypeScript: Mongoose models/schemas, Sequelize models, TypeORM entities, Prisma schema, Knex migrations - -Identify: -- Entity/model classes with their fields, types, and constraints — note the source file path for each entity -- Transaction management annotations/configuration (`@Transactional`, `TransactionScope`, etc.) -- Bidirectional vs unidirectional relationship mappings (e.g., `owner.addPet(pet)` establishing parent-child links) - -**Diagram — Mermaid `erDiagram`:** -- Show primary entities with key fields (PK, FK) -- Use standard cardinality notation: `||--o{` (one-to-many), `||--||` (one-to-one), `}o--o{` (many-to-many) -- Group related entities logically -- Include relationship labels -- Annotate which service owns each entity group (use comments or subgraph labels) - -Example: - -~~~mermaid -erDiagram - Owner ||--o{ Pet : "has" - Pet ||--o{ Visit : "has" - Pet }o--|| PetType : "is of" - Vet }o--o{ Specialty : "has" - Owner { - int id PK - string firstName - string lastName - string address - string city - string telephone - } - Pet { - int id PK - string name - date birthDate - int ownerId FK - int typeId FK - } - PetType { - int id PK - string name - } - Visit { - int id PK - int petId FK - date visitDate - string description - } - Vet { - int id PK - string firstName - string lastName - } - Specialty { - int id PK - string name - } -~~~ - -### Step 4: Generate Key Repository Methods Section - -For each service/module, document the key repository interfaces and produce the complete `## Key Repository Methods` section: - -- Repository interface name, entity type, and source file path -- Standard CRUD methods inherited from base interface -- Custom query methods with their signatures and purposes — especially: - - Bulk/batch queries (e.g., `findByPetIdIn(Collection)`) used for cross-service aggregation - - Custom finders with derived query methods - - Named queries or `@Query`-annotated methods - - Raw SQL or stored procedure calls -- Query method parameters and return types - -### Step 5: Generate Caching Strategy Section - -Identify caching layers and configuration and produce the complete `## Caching Strategy` section: - -- Cache providers: EhCache, Redis, Caffeine, Spring Cache (`@Cacheable`, `@CacheEvict`), MemoryCache, IDistributedCache -- Cache configuration: TTL, eviction policies, cache regions/names -- Cache-aside, read-through, write-through, write-behind patterns -- Session caching, query result caching, second-level cache (Hibernate) -- Rationale for caching decisions (e.g., "veterinarian data is read frequently but changes rarely") -- JSR-107 (JCache) / `cache-api` usage and provider binding - -### Step 6: Generate Data Ownership Boundaries Section - -Document data-store topology and cross-service access semantics, plus data classification, then produce the complete `## Data Ownership Boundaries` section (including the `### Data Classification & Sensitivity` subsection): - -**Boundaries:** -- Shared vs isolated data stores (shared database, database-per-service, logical separation within shared DB) -- Cross-service data access patterns: how one service queries another service's data (direct DB access vs REST API calls vs batch/bulk query methods such as `findByPetIdIn(...)` that enable gateway-level aggregation) -- Read/write patterns and CQRS observations across services - -**Data Classification & Sensitivity (`### Data Classification & Sensitivity` subsection):** -- Identify whether stored data contains sensitive categories — PII (names, addresses, phone numbers, emails), PHI (health records), PCI (payment card data) -- For each sensitive category found, note whether encryption-at-rest, data masking, or field-level access controls are in place -- If absent, state this explicitly (e.g., "Owner entity stores PII (firstName, lastName, address, telephone); no encryption-at-rest or masking configured") - -### Step 7: Save Output - -Save to `.github/modernize/assessment/engines/data-architecture.md` with this exact structure: - -``` -# Data Architecture & Persistence Layer - -A brief introduction (1-2 sentences) summarizing the data layer. - -## Database Configuration - -[Table: Service/Module | DB Type | Profile | Driver | Connection | Migration Tool] - -## Data Ownership per Service - -[Table: Service | Tables Owned | ORM Framework | Caching | Notes] - -## Entity Model - -< Mermaid erDiagram here > - -## Key Repository Methods - -[Table: Service | Repository | Notable Methods | Purpose] - -## Caching Strategy - -[Table or description of caching layers, providers, TTL, patterns, and rationale] - -## Data Ownership Boundaries - -[Description of shared vs isolated data stores, cross-service data access patterns, and aggregation enablers] - -### Data Classification & Sensitivity - -[Table: Entity | Sensitive Fields | Classification (PII/PHI/PCI/None) | Controls in Place] -[If no sensitive data found: "No PII, PHI, or PCI data detected in entity model."] -``` - -## Scaling Rules - -- If the project has **more than 30 entities**, aggregate minor entities and show only the core domain model (15-20 key entities) -- Keep the ER diagram under **40 entities** to ensure readability and GitHub rendering compatibility -- For multi-module projects, focus on inter-module entity relationships and data boundaries -- Collapse join tables into relationship annotations rather than showing them as separate entities -- In the repository methods table, focus on non-CRUD custom methods; omit standard inherited methods - -## Mermaid Syntax Rules - -- Use `erDiagram` -- Avoid special characters (`@`, `#`, `$`, `%`, `&`) in entity/field names — use plain text -- Use standard cardinality: `||--o{`, `||--||`, `}o--o{`, `|o--o{` -- Use quoted relationship labels: `Owner ||--o{ Pet : "has"` -- Do not use backticks inside entity or field names -- **Never put `{` or `}` inside a quoted attribute description or relationship label.** Mermaid's ER parser treats `{` as the entity-body opener even inside quotes, which crashes the entire diagram with "Syntax error in text". Avoid placeholder syntax like `{userId}`, `{path}`, or `{id}` in attribute comments — write `userId`, `path`, `id` (or describe in plain words: "indexed by user id"). Example: - - ❌ `string Key PK "Redis key /basket/{BuyerId}"` - - ✅ `string Key PK "Redis key /basket/"` or `"Redis key prefixed with /basket/"` - -## Error Handling - -- **Unsupported project type**: Output a single line: `> ERROR: Unsupported project type. This skill supports Java, .NET, JavaScript, and TypeScript projects only.` -- **No data access layer found**: Output: `> ERROR: No recognized data access patterns or entities found at {workspace-path}. Verify the path is correct.` -- **Insufficient info**: Generate a best-effort diagram from available data. Add a note: `> Note: Some entities or relationships could not be fully identified.` - -## Success Criteria - -- Database configuration table lists all discovered databases with type, profile, driver, and migration tools -- Data ownership table maps each service to its owned tables, ORM, and caching layer -- Mermaid ER diagram renders correctly showing entity relationships with cardinality and key fields -- Repository methods table documents custom query methods with purposes, especially cross-service aggregation enablers -- Caching strategy section describes cache providers, patterns, and rationale -- Data ownership boundaries describe shared vs isolated stores and cross-service data access patterns -- Data Classification & Sensitivity table identifies PII/PHI/PCI fields and documents presence or absence of controls -- File saved to `.github/modernize/assessment/engines/data-architecture.md` diff --git a/.github/skills/dependency-map/SKILL.md b/.github/skills/dependency-map/SKILL.md deleted file mode 100644 index 60a929e78..000000000 --- a/.github/skills/dependency-map/SKILL.md +++ /dev/null @@ -1,180 +0,0 @@ ---- -name: dependency-map -description: Generate dependency map diagram from project build files ---- - -# Dependency Map - -Analyze project build and package files to generate a visual map of all external dependencies grouped by functional category. Save to `.github/modernize/assessment/engines/dependency-map.md`. - -This skill focuses exclusively on **declared external dependencies** (libraries, frameworks, packages). For internal application structure and component relationships, see the `architecture-diagram` skill. - -## Input Parameters - -- `workspace-path` (optional): Path to the project to analyze (defaults to current directory) - -## Execution Steps - -### Step 1: Generate Dependencies Section - -Analyze build files and produce the complete `## Dependencies` section in one pass: - -**Analysis — examine only build and package management files** (do NOT scan source code — that is the `architecture-diagram` skill's job): - -- Java: pom.xml, build.gradle, settings.gradle, gradle.properties, gradle lockfiles -- .NET: *.csproj, Directory.Build.props, packages.config, Directory.Packages.props -- JavaScript/TypeScript: package.json, package-lock.json, yarn.lock, pnpm-lock.yaml - -For each dependency extract: -- Group/package name and artifact name -- Declared version (or version range) -- Scope (compile, runtime, test, provided) - -Also detect: -- Parent POM / BOM imports (Java) -- Central package management (.NET Directory.Packages.props) -- Transitive dependencies where visible from lock files or BOM - -**Categorize** dependencies into functional groups: - -| Category | Examples | -|----------|----------| -| Web Frameworks | Spring Web, ASP.NET Core MVC, JAX-RS | -| Database / ORM | Hibernate, Entity Framework, JDBC drivers | -| Messaging | Kafka client, RabbitMQ, Azure Service Bus | -| Caching | Redis, EhCache, MemoryCache | -| Logging | SLF4J, Log4j, Serilog, NLog | -| Security | Spring Security, Microsoft.Identity, OAuth libs | -| Observability | Micrometer, OpenTelemetry, Application Insights | -| Utilities | Guava, Apache Commons, Lombok, AutoMapper | - -Rules: -- Exclude test-scoped dependencies (JUnit, xUnit, Mockito, etc.) from the main diagram and Dependency Summary table — they are not relevant for modernization planning -- If a dependency doesn't fit any category, put it under "Utilities" -- Collect test-scoped dependencies separately for the Test Dependencies section (Step 2) - -**Diagram — Mermaid `flowchart LR`:** -- Application as the central left-side node -- One `subgraph` per functional category -- Each dependency as a node showing name and version: `Lib["Library Name v1.2.3"]` -- Arrows from Application to each category subgraph -- If a BOM/parent POM manages versions, show it as a separate node linked to the dependencies it governs - -Example: - -~~~mermaid -flowchart LR - App["MyApplication"] - - subgraph Web["Web Frameworks"] - SpringMVC["Spring MVC 5.3.x"] - Thymeleaf["Thymeleaf 3.0"] - end - subgraph DB["Database / ORM"] - Hibernate["Hibernate 5.6"] - PgDriver["PostgreSQL Driver 42.6"] - end - subgraph Messaging - Kafka["Kafka Client 3.4"] - end - subgraph Cache["Caching"] - Redis["Jedis 4.3"] - end - subgraph Log["Logging"] - SLF4J["SLF4J 1.7"] - Logback["Logback 1.2"] - end - subgraph Sec["Security"] - SpringSec["Spring Security 5.7"] - end - subgraph Util["Utilities"] - Lombok["Lombok 1.18"] - Jackson["Jackson 2.14"] - end - - App -->|"web"| Web - App -->|"persistence"| DB - App -->|"messaging"| Messaging - App -->|"caching"| Cache - App -->|"logging"| Log - App -->|"security"| Sec - App -->|"utilities"| Util - SLF4J -.->|"implementation"| Logback -~~~ - -**Textual explanations (write immediately after the diagram):** -- **Dependency Summary table**: Category | Count | Key Libraries | Notes (e.g., Web Frameworks | 2 | ASP.NET MVC 5.2.7, Razor 3.2.7 | Legacy MVC stack on .NET Framework) -- **Version & Compatibility Risks**: A short paragraph highlighting dependencies that are outdated, end-of-life, or have known migration concerns (e.g., ".NET Framework 4.7.2 is in maintenance mode; Entity Framework 6 has a migration path to EF Core") -- **Notable Observations**: 2-4 bullet points on anything noteworthy — duplicate functionality across libraries, deprecated packages, security-sensitive dependencies, or unusually large transitive trees - -### Step 2: Generate Test Dependencies Section - -Collect all test-scoped dependencies (excluded from the main diagram) and produce the complete `## Test Dependencies` section: - -- List detected test frameworks and supporting libraries with their versions (e.g., JUnit 5, Mockito, AssertJ, Testcontainers, xUnit, Jest) -- Report the total number of test-scope dependencies -- Note any test infrastructure concerns (e.g., outdated test framework version, missing contract-testing library, no integration test framework detected) - -### Step 3: Save Output - -Save to `.github/modernize/assessment/engines/dependency-map.md` with this exact structure: - -``` -# Dependency Map - -A brief introduction (1-2 sentences) stating project name and total dependency count. - -## Dependencies - -< Mermaid flowchart LR here > - -### Dependency Summary - -[Table: Category | Count | Key Libraries | Notes] - -### Version & Compatibility Risks - -[Short paragraph on outdated or end-of-life dependencies] - -### Notable Observations - -[2-4 bullet points on noteworthy findings] - -## Test Dependencies - -[Table: Framework | Version | Notes] - -Total test-scope dependencies: N -[1-2 sentences on test infrastructure observations, or "No test dependencies detected."] -``` - -## Scaling Rules - -- If the project has **more than 50 declared dependencies**, collapse minor utilities into a single aggregate node (e.g., `Utils["12 utility libraries"]`) and only show individually the top dependencies by importance -- Keep the diagram under **40 nodes** to ensure readability and GitHub rendering compatibility -- For multi-module projects (e.g., multi-module Maven/Gradle, multi-project .sln), show shared dependencies once and module-specific dependencies grouped by module - -## Mermaid Syntax Rules - -- Use `flowchart LR` -- Avoid special characters (`@`, `#`, `$`, `%`, `&`) in node labels — use plain text -- Always quote arrow labels with double quotes: `-->|"label"|` -- Use `subgraph` for grouping, with a display name in quotes if it contains spaces -- Use `-.->` (dotted arrow) for transitive/indirect relationships -- Verify all node IDs are unique across the entire diagram - -## Error Handling - -- **Unsupported project type**: Output a single line: `> ERROR: Unsupported project type. This skill supports Java, .NET, JavaScript, and TypeScript projects only.` -- **No build files found**: Output: `> ERROR: No recognized build files found at {workspace-path}. Verify the path is correct.` -- **Incomplete dependency info**: Generate a best-effort diagram from available data. Add a note inside the diagram: `Note["Some dependencies could not be fully resolved"]` - -## Success Criteria - -- Mermaid diagram renders correctly with dependencies grouped by functional category -- Each dependency shows name and version -- Dependency Summary table lists categories with counts and key libraries -- Version & Compatibility Risks paragraph highlights outdated or end-of-life dependencies -- Notable Observations lists 2-4 noteworthy findings -- Test Dependencies section lists detected test frameworks with versions and total count -- File saved to `.github/modernize/assessment/engines/dependency-map.md` diff --git a/.github/skills/dotnet-upgrade-assessment/SKILL.md b/.github/skills/dotnet-upgrade-assessment/SKILL.md deleted file mode 100644 index 6e6e6aba7..000000000 --- a/.github/skills/dotnet-upgrade-assessment/SKILL.md +++ /dev/null @@ -1,149 +0,0 @@ ---- -name: dotnet-upgrade-assessment -description: Runs .NET upgrade assessment only. Does not create plans, execute upgrades, or modify source code. ---- - -# .NET Upgrade Assessment Skill - -This skill runs a .NET upgrade assessment using the MCP server. It performs ONLY assessment — no plans, no code changes, no upgrades. - -## Input Parameters - -- `workspace-path` (optional): Path to the project root containing the `.sln` or `.slnx` file. Defaults to `.` (current directory). For a repository with multiple solutions, pass the solution folder path. -- `target-framework` (optional): Target .NET framework version for upgrade (e.g., `net8`, `net9`, `net10`). - -## Prerequisites — MCP Tool Availability Check - -Before doing anything else, verify that ALL of the following MCP tools are available: -- `get_state` -- `get_scenarios` -- `initialize_scenario` -- `generate_dotnet_upgrade_assessment` - -**If ANY of these tools are NOT available, STOP immediately.** Do NOT attempt to perform the assessment manually, do NOT analyze code yourself, and do NOT use alternative approaches. Simply report that the required MCP tools are unavailable and exit. - -## Prerequisites — Locate the Report Directory - -Before calling any MCP tools, find the versioned report directory that was created by the core assessment skill. The directory is at: - -``` -{workspace-path}/.github/modernize/assessment/reports/report-{reportId}/ -``` - -Where `reportId` is a 14-digit timestamp in `yyyyMMddHHmmss` format (e.g., `20250514065424`). Find the latest one: - -```bash -REPORT_DIR=$(ls -d {workspace-path}/.github/modernize/assessment/reports/report-[0-9]* 2>/dev/null | sort | tail -1) -``` - -If `REPORT_DIR` is empty (no `report-*` directory exists), report an error and STOP — the core assessment must run first. - -## Prerequisites — ua-settings.json Setup - -Create the settings file pointing to the discovered report directory: - -```bash -mkdir -p {workspace-path}/.github/modernize -cat > {workspace-path}/.github/modernize/ua-settings.json << 'EOF' -{ - "outputPath": "{REPORT_DIR}" -} -EOF -export UA_SETTINGS_FILE_PATH="$(realpath {workspace-path}/.github/modernize/ua-settings.json)" -``` - -Replace `{workspace-path}` with the actual parameter value and `{REPORT_DIR}` with the discovered report directory path from the previous step. - -## Execution Steps - -Execute these steps IN ORDER. Do NOT skip any step. Do NOT deviate from this sequence. - -### Step 1: Call `get_state()` - -Call the MCP tool `get_state()` to initialize the workflow state. - -### Step 2: Call `get_scenarios()` - -Call the MCP tool `get_scenarios()`. From the response, identify the scenario for ".NET version upgrade" and note its `scenarioId`. - -### Step 3: Call `initialize_scenario` - -Call the MCP tool `initialize_scenario` with: -- `scenarioId`: the ID found in Step 2 -- `description`: "Upgrade assessment for {solution file name}" - -### Step 4: Find the solution file or folder - -Look for `.sln` or `.slnx` files in `{workspace-path}`. Use the first one found. -If no solution file exists, use projects mode with `{workspace-path}` directly. - -### Step 5: Call `generate_dotnet_upgrade_assessment` - -Call `generate_dotnet_upgrade_assessment` on the selected solution, project, folder, or project list. Scope the assessment to the user's selected projects when the request is not solution-wide. -If `target-framework` was provided as an input parameter, pass it as `targetFramework` in the request as well. - -For a solution-wide assessment: - -```json -{ - "inputMode": "solution", - "paths": "{absolute-path-to-solution}", - "targetFramework": "net10.0" -} -``` - -For scoped project assessments, pass one or more absolute project paths as a semicolon-delimited string: - -```json -{ - "inputMode": "projects", - "paths": "{absolute-path-to-project-1};{absolute-path-to-project-2}", - "targetFramework": "net10.0" -} -``` - -For folder assessments, pass the absolute folder path: - -```json -{ - "inputMode": "folder", - "paths": "{absolute-path-to-folder}", - "targetFramework": "net10.0" -} -``` - - - -### Step 6: Verify scenario output files - -Confirm that the MCP server wrote assessment files under `{REPORT_DIR}/scenarios/dotnet-version-upgrade/`. Expected files include: -- `assessment.json` -- `assessment.csv` -- `assessment.md` -- `scenario.json` -- `dependencies-health.json` - -If the directory or files are missing, report what was generated and note the discrepancy. - -### Step 7: Return the result - -Report the assessment output file path (`{REPORT_DIR}/scenarios/dotnet-version-upgrade/`). STOP. - -## Cleanup - -After the assessment completes (whether successful or failed), remove the settings file: - -```bash -rm -f {workspace-path}/.github/modernize/ua-settings.json -``` - -## Rules - -- **ALL work MUST go through MCP tool calls.** Do NOT analyze code yourself. -- **Do NOT create plans or modify source files.** This is assessment only. -- **Do NOT ask for user input.** Run all steps in one session without pausing. -- **If a tool call fails, report the exact error.** Do NOT work around it or retry with different parameters. -- **Do NOT load additional skills or instructions.** Follow only the steps above. -- **Do NOT call `get_instructions`, `start_task`, `complete_task`, `break_down_task`, or any task/workflow management tools.** Those are for upgrade execution, not assessment. -- **Do NOT create git branches, commits, or source control operations.** -- **Do NOT read or analyze source code files.** The MCP tools handle all analysis internally. diff --git a/.github/skills/execute-java-upgrade-task/SKILL.md b/.github/skills/execute-java-upgrade-task/SKILL.md deleted file mode 100644 index d6957ae37..000000000 --- a/.github/skills/execute-java-upgrade-task/SKILL.md +++ /dev/null @@ -1,431 +0,0 @@ ---- -name: execute-java-upgrade-task -description: Execute a Java upgrade task as part of a modernization plan ---- - -You are an expert Java upgrade agent. **Task**: Upgrade to user-specified target versions by (1) generating an incremental plan and (2) executing it per the rules below. - -You MUST generate the upgrade plan and execute it by yourself following the rules and workflow. You are now in the "modernize-java" agent. You MUST NOT call `#generate-upgrade-plan` or `#redirect-to-upgrade-agent` again as it will redirect to you, causing an infinite loop. - -## Rules - -### Upgrade Success Criteria (ALL must be met) - -- **Goal**: All user-specified target versions met. -- **Compilation**: Both main source code AND test code compile successfully = `mvn clean test-compile` (or equivalent) succeeds. This includes compiling production code and all test classes. -- **Test**: **100% test pass rate** = `mvn clean test` succeeds. Minimum acceptable: test pass rate ≥ baseline (pre-upgrade pass rate). Every test failure MUST be fixed unless proven to be a pre-existing flaky test (documented with evidence from baseline run). **Skip if user set "Run tests before and after the upgrade: false" in plan.md Options.** - -### Anti-Excuse Rules (MANDATORY) - -- **NO premature termination**: Token limits, time constraints, or complexity are NEVER valid reasons to skip fixing test failures. -- **NO "close enough" acceptance**: 95% is NOT 100%. Every failing test requires a fix attempt with documented root cause. -- **NO deferred fixes**: "Fix post-merge", "TODO later", "can be addressed separately" are NOT acceptable. Fix NOW or document as a genuine unfixable limitation with exhaustive justification. -- **NO categorical dismissals**: "Test-specific issues", "doesn't affect production", "sample/demo code", "non-blocking" are NOT valid reasons to skip fixes. ALL tests must pass. -- **NO blame-shifting**: "Known framework issue", "migration behavior change", "infrastructure problem" require YOU to implement the fix or workaround, not document and move on. -- **Genuine limitations ONLY**: A limitation is valid ONLY if: (1) multiple distinct fix approaches were attempted and documented, (2) root cause is clearly identified, (3) fix is technically impossible without breaking other functionality. - -### Review Code Changes (MANDATORY for each step) - -After completing changes in each step, review code changes per the rules in `progress.md` templates BEFORE verification. Key areas: - -- **Sufficiency**: all required upgrade changes are present -- **Necessity**: no CRITICAL unnecessary changes — Unnecessary changes that do not affect behavior may be retained; however, it is essential to ensure that the functional behavior remains consistent and security controls are preserved. - -### Upgrade Strategy - -- **Incremental upgrades**: Stepwise dependency upgrades; use intermediates to avoid large jumps breaking builds. -- **Minimal changes**: Only upgrade dependencies essential for compatibility with target versions. -- **Risk-first**: Handle EOL/challenging deps early in isolated steps. -- **Necessary/Meaningful steps only**: Each step MUST change code/config. NO steps for pure analysis/validation. Merge small related changes. **Test**: "Does this step modify project files?" -- **Automation tools**: Use automation tools like OpenRewrite etc. for efficiency; always verify output. -- **Successor preference**: Compatible successor > Adapter pattern > Code rewrite. -- **Build tool compatibility**: Check Maven/Gradle version compatibility with the target JDK. Upgrade the build tool (including wrapper) if the current version does not support the target JDK. Common minimum versions: Maven 3.9+ / Gradle 8.5+ for Java 21, Maven 4.0+ / Gradle 9.1+ for Java 25. When a wrapper (`mvnw`/`gradlew`) is present, also upgrade the wrapper-defined version in `.mvn/wrapper/maven-wrapper.properties` or `gradle/wrapper/gradle-wrapper.properties`. -- **Temporary errors OK**: Steps may pass with known errors if resolved later or pre-existing. -- **CVE version pin protection**: Before removing or downgrading any explicit `` override in `` or ``, verify it is not a CVE-driven pin. Check for: (1) nearby XML comments referencing CVE IDs or security fixes, (2) whether the pinned version is **newer** than the BOM-managed version — if so, it likely exists to patch a vulnerability. When in doubt, **keep the override** and document the decision. - -### Execution Guidelines - -- **Wrapper preference**: Use Maven Wrapper (`mvnw`/`mvnw.cmd`) or Gradle Wrapper (`gradlew`/`gradlew.bat`) when present in the project root, unless user explicitly specifies otherwise. This ensures consistent build tool versions across environments. -- **Version control via tool**: 🛑 NEVER use direct `git` commands in terminal — ONLY use `#version-control` for ALL version control operations (check status, create branch, commit, stash, discard changes). **ALWAYS pass `sessionId: `** to every `#version-control` call for telemetry tracking. When `GIT_AVAILABLE=false` (git not installed or project is not a git repository), skip ALL version control operations. Files remain uncommitted in the working directory. Use `N/A` for `` and `` placeholders. Record a notice in `plan.md` that changes are not version-controlled during this upgrade. -- **Version control timing**: `#version-control` requires `SESSION_ID` which is only available after Phase 1 (Precheck) succeeds. Do NOT use `#version-control` during Precheck. Git availability detection is deferred to Phase 2 Initialize. -- **Template compliance**: For `plan.md`, follow the **Plan Format Specification** below and write the complete file in a **single `create_file` call** — do NOT read a template or use `insert_edit_into_file` during plan generation. For `progress.md`, follow the rules and samples in each section's HTML comments of the template file. For `summary.md`, read `summary.template.md` (in the session directory) as a spec, then write `summary.md` as a new file using `create_file`. -- **Uninterrupted run**: Complete each phase fully without pausing for user input, except for the mandatory user confirmation after plan generation (Phase 3). -- **User input**: Prefer `#askQuestions` tool when available to collect user input (e.g., choices, confirmations). Fall back to plain-text prompts only when `#askQuestions` is unavailable. - -### Event Reporting (MANDATORY) - -Call `#report-event` immediately at each key milestone. **NO skipping. NO batching. This is non-negotiable.** - -- **When**: Report at every milestone defined in the Workflow phases — do not wait until the end of a phase. -- **Details**: Pass `details` for `precheckCompleted` (on both success and failure — see Phase 1), `environmentSetup`, `upgradeStepStarted`, and `upgradeStepCompleted`. -- **Status values**: `"succeeded"` | `"failed"` (must include `message`) | `"skipped"` (must include `message`). -- **SILENT**: Event reporting is internal telemetry only — NEVER mention `#report-event` calls, event names, or reporting status in user-facing messages. - -### Efficiency - -- **Targeted reads**: Use `grep` over full file reads; read sections, not entire files. -- **Quiet commands**: Use `-q`, `--quiet` for build/test when appropriate. -- **Single write for plan.md**: Generate the complete `plan.md` in one `create_file` call after gathering all information. Do NOT make multiple edits. -- **Incremental writes for progress.md**: Update `progress.md` incrementally as steps complete. - -### Session ID Consistency (CRITICAL) - -- `SESSION_ID` is generated in Phase 1 (Precheck) on success. Use this **exact** ID for ALL subsequent tool calls — never fabricate or change it. - -### Intermediate Version Strategy - -Use intermediates **when direct upgrade risks breaking builds**. A good intermediate has: - -- **Stability**: Stable LTS release with production track record -- **Compatibility bridge**: Bridges compatibility between current deps AND intermediates of other deps - -**Example**: Spring Boot 2.7.x is an effective intermediate for `Spring Boot 1.x → 3.x` because: - -- Final stable 2.x release (stability ✓) -- Supports Java 8-21 (wide compatibility range ✓) -- Uses javax.servlet (compatible with 1.x/2.x) with migration path to jakarta (3.x) ✓ - -Consider dependencies holistically — use target framework/Java as reference for intermediates. - -### Version Knowledge - -LLM training data may be outdated regarding the latest Java and Spring Boot releases. **Never reject a target version solely based on training data knowledge.** - -1. **Known stable/LTS versions to suggest by default** (non-exhaustive — newer stable or LTS releases may exist beyond this list): - - Java LTS: 11, 17, 21, 25 - - Spring Boot stable release lines: 2.7.x, 3.5.x, 4.0.x -2. **When the user requests a version you don't recognize**: Your training data may be stale. Use the `fetch` tool to verify the latest release information from the web before making any judgment. Only reject a version as invalid if the web lookup confirms it does not exist. Never reject based solely on training data. - -## Plan Format Specification - -When writing `plan.md`, generate the **complete file** in a single `create_file` call to `.github/java-upgrade//plan.md`. Follow this exact structure: - -### Plan Header - -```markdown -# Upgrade Plan: () - -- **Generated**: -- **HEAD Branch**: -- **HEAD Commit ID**: -``` - -### Section: Available Tools - -List ONLY the JDKs and build tools required/used during the upgrade (not all discovered ones). Use `#list-jdks` and `#list-mavens` results to check availability. Mark missing required JDKs as `****` with a note indicating which step needs it. **Exception — base (current) JDK**: If the project's current JDK version is not found, do NOT mark it as ``. The base JDK is only needed for the optional baseline step; if the user doesn't have it, baseline will be skipped. Note it as "not available (baseline will be skipped)". Mark build tools needing upgrade as `****`. If a wrapper is present, check the wrapper-defined version in `.mvn/wrapper/maven-wrapper.properties` or `gradle/wrapper/gradle-wrapper.properties`. Installation/upgrade happens during execution, not planning. - -**Build tool compatibility reference** (non-exhaustive — verify from official docs when uncertain): -- Maven 3.9+: required for Java 21 -- Maven 4.0+: required for Java 25 -- Gradle 8.5+: required for Java 21 -- Gradle 8.8+: required for Java 22 -- Gradle 9.1+: required for Java 25 -- maven-compiler-plugin 3.11+: required for Java 21 -- maven-surefire-plugin 3.1+: recommended for Java 17+ - -This section is finalized during Design & Review (after step sequence is known), not during Initialize & Analyze. - -Sample: -```markdown -## Available Tools - -**JDKs** -- JDK 1.8.0: /path/to/jdk-8 (current project JDK, used by step 2) -- JDK 17: **** (required by step 3) -- JDK 21: **** (required by step 6) - -**Build Tools** -- Maven 3.9.6: /path/to/maven -- Maven Wrapper: 3.8.1 → **** to 3.9.6+ (current version incompatible with Java 21) -``` - -### Section: Guidelines - -User-specified guidelines or constraints in bullet points. Extract from user's prompt if provided, or leave empty. - -Always include this user-facing note: -```markdown -> Note: You can add any specific guidelines or constraints for the upgrade process here if needed, bullet points are preferred. -``` - -### Section: Options - -```markdown -## Options - -- Working branch: appmod/java-upgrade- -- Run tests before and after the upgrade: true -``` - -These are user-configurable options. Never remove them. - -### Section: Upgrade Goals - -List ONLY user-requested target versions. These drive all other decisions. - -### Section: Technology Stack - -Table of core dependencies and compatibility with upgrade goals. Analyze ALL modules in multi-module projects. Only include direct dependencies + those critical for upgrade compatibility. Flag EOL dependencies with "⚠️ EOL" suffix. Include build tools and plugins. - -Columns: Technology/Dependency | Current | Min Compatible Version | Why Incompatible - -Sample: -```markdown -| Technology/Dependency | Current | Min Compatible | Why Incompatible | -| ------------------------ | ------- | -------------- | ---------------------------------------------- | -| Java | 8 | 21 | User requested | -| Spring Boot | 2.5.0 | 3.2.0 | User requested | -| Maven (wrapper) | 3.6.3 | 3.9.0 | Maven 3.6.x does not support Java 21 | -| maven-compiler-plugin | 3.8.1 | 3.11.0 | Older versions cannot compile Java 21 bytecode | -| javax.servlet ⚠️ EOL | 4.0 | N/A | Replaced by jakarta.servlet in Spring Boot 3.x | -| Lombok | 1.18.20 | 1.18.20 | - | -``` - -### Section: Derived Upgrades - -Required upgrades inferred from user targets based on compatibility rules. Each must have justification. - -Common derivations: -- Spring Boot 3.x → Java 17+, Jakarta EE 9+, Hibernate 6.x, Spring Framework 6.x -- Spring Boot 3.2+ → Spring Framework 6.1+ -- Spring Boot 4.x → Java 17+, Jakarta EE 10+, Spring Framework 7.x -- Java 21 → Maven 3.9+, Gradle 8.5+, maven-compiler-plugin 3.11+ -- Java 25 → Maven 4.0+, Gradle 9.1+ -- Build tool upgrade → update wrapper version - -### Section: Impact Analysis - -**This is the core of the plan.** A complete, file-level specification of every change required by the upgrade. Aim for completeness so the execution phase can apply changes by following this section alone. - -Organize findings into subsections: - -#### Subsection: Dependency Changes - -Table of all dependency/plugin/BOM changes in build files. - -Columns: File | Dependency | Current | Action | Target | Reason - -Actions: `upgrade`, `replace` (different artifact), `remove` (pin/override), `add` (new dep needed) - -Sample: -```markdown -| File | Dependency | Current | Action | Target | Reason | -|------|-----------|---------|--------|--------|--------| -| pom.xml | spring-boot-starter-parent | 2.7.18 | upgrade | 3.3.13 | User requested | -| pom.xml | java.version | 11 | upgrade | 21 | User requested | -| pom.xml | logback.version pin | 1.2.12 | remove | (managed) | Conflicts with SB 3.3 managed 1.5.x | -| app-service/pom.xml | httpclient | 4.5.x | replace | httpclient5 | Removed in Spring Framework 6.0 | -``` - -#### Subsection: Source Code Changes - -Table of all source file modifications required. - -Columns: File | Location | Current | Required Change | Reason - -Sample: -```markdown -| File | Location | Current | Required Change | Reason | -|------|----------|---------|----------------|--------| -| UserController.java | import line 3 | javax.servlet.http.HttpServletRequest | Replace with: jakarta.servlet.http.HttpServletRequest | Jakarta EE 10 namespace | -| SecurityConfig.java | class declaration | extends WebSecurityConfigurerAdapter | Rewrite to @Bean SecurityFilterChain | Removed in Spring Security 6.0 | -| HttpClientService.java | imports + API calls | org.apache.http.* | Rewrite to org.apache.hc.client5.* | HttpClient 4→5 API change | -``` - -#### Subsection: Configuration Changes - -Table of changes to config files (`application.properties`, `application.yml`, XML configs, etc.). Omit if no config changes needed. - -Columns: File | Property/Setting | Current | Required Change | Reason - -#### Subsection: CI/CD Changes - -Table of CI/CD file updates needed to match the upgraded JDK/runtime. - -Columns: File | Location | Current | Required Change - -Sample: -```markdown -| File | Location | Current | Required Change | -|------|----------|---------|----------------| -| Dockerfile | line 1, 12 | mcr.microsoft.com/openjdk/jdk:11-ubuntu | Change to: jdk:21-ubuntu | -| azure-pipelines.yml | line 8 | versionSpec: '11' | Change to: '21' | -``` - -#### Subsection: Risks & Warnings - -Items requiring special attention during execution — non-trivial rewrites, potential runtime-only issues, CVE-pinned versions, etc. Include genuine risks and medium-risk items with non-trivial mitigation, not routine changes. Include a mitigation strategy for each risk. - -Sample: -```markdown -- **SecurityConfig rewrite**: Non-trivial — Spring Security 6 DSL changed significantly (authorizeRequests → authorizeHttpRequests, antMatchers → requestMatchers, lambda DSL for CSRF). **Mitigation**: Verify security behavior with existing integration tests after rewrite; add a smoke test if none exist. -- **JDK reflection usage in FooService.java:42**: `setAccessible(true)` on java.lang.reflect.Field — compiles but throws InaccessibleObjectException at runtime on JDK 17+. No test coverage for this path. **Mitigation**: Apply deterministic rewrite using `MethodHandles.privateLookupIn()`, or add `--add-opens` as a temporary workaround with a TODO to remove it. -``` - -### Section: Upgrade Steps - -Step format: -```markdown -- Step N: - - **Rationale**: Why this step is needed and why at this position - - **Changes to Make**: Reference specific items from Impact Analysis - - **Verification**: Command, JDK, Expected Result -``` - -**Step design rules:** -- **Every step must leave the project in a compilable state.** Do not create steps that expect compilation failure — group related changes together so each step compiles cleanly. For multi-module projects, verify compilation at the reactor root level if individual module verification is impractical. -- **Reference Impact Analysis, don't duplicate it.** Steps should reference specific subsections or groups (e.g., "Apply all Dependency Changes for Spring Boot 3.x migration and corresponding Source Code Changes") rather than repeating every file change. -- **Fewer, coarser steps.** Group related changes (e.g., all Spring Boot 3.x migration changes in one step) rather than one step per file. - -**Mandatory steps:** - -- **Step 1 (MANDATORY)**: Setup Environment — Install required JDKs/build tools marked `` (do NOT install the base JDK if it is unavailable — it is only needed for the optional baseline). Verify with `#list-jdks`. Expected: All required JDKs available. -- **Step 2 (MANDATORY)**: Setup Baseline — If the base (current) JDK is available, run baseline compilation and tests with current JDK. Command: `mvn clean compile test-compile -q && mvn clean test -q`. Document SUCCESS/FAILURE, test pass rate (forms acceptance criteria). **If the base JDK is not available, skip this step** with status `"skipped"` and proceed to upgrade steps. -- **Steps 3-N**: Upgrade steps — apply all changes from Impact Analysis. Group related changes so each step compiles. Verify with `mvn clean test-compile -q` (compile only). -- **Final step (MANDATORY)**: Final Validation — Verify all goals met, resolve ALL TODOs and workarounds, clean rebuild with target JDK, run full test suite and fix ALL failures (iterative fix loop until 100% pass). Skip tests if disabled in Options. **For files flagged in Risks & Warnings as lacking test coverage, "compile + test pass" is NOT sufficient** — either (a) apply the deterministic rewrite as part of an upgrade step, or (b) document the residual runtime risk in `summary.md` Key Risks. Do not silently ship a latent JDK-version runtime bug. - -## Workflow - -### Phase 1: Precheck - -| Category | Scenario | Action (use `#askQuestions` tool when available and appropriate) | -| ------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Unsupported Project | Not a Java project | This path should not be reached — the upgrade agent is only invoked for Java projects. Do NOT call `#report-event`. Simply STOP and inform the user: "This project does not appear to be a Java project. The Java upgrade agent only supports Java projects." | -| Unsupported Project | Not a Maven/Gradle project | Check for alternative build systems: look for `build.xml` (Ant), `BUILD`/`BUILD.bazel` (Bazel), or other build files. If detected, call `#report-event` with details, then inform the user: "Detected [Ant/Bazel/other] build system. Maven and Gradle are fully supported; [Ant/Bazel/other] support is experimental and results may vary." Attempt to continue with best-effort analysis. If no recognizable build system is found, call `#report-event`, then STOP with error listing supported build systems (Maven, Gradle). | -| Invalid Goal | Missing target version | Do NOT call `#report-event` yet. Instead, analyze project dependencies (read `pom.xml`/`build.gradle` to detect current Java version, Spring Boot version, and other key deps), derive feasible upgrade options (e.g., Java 17, Java 21, Java 25, Spring Boot 3.2, Spring Boot 3.5, Spring Boot 4.0), and use `#askQuestions` to present those options as selectable choices for the user to pick the desired target(s). Only report `precheckCompleted` (succeeded or failed) after the user has selected a target or the interaction concludes. | -| Invalid Goal | Incompatible target combination | Call `#report-event`, then STOP and explain incompatibility | - -**On failure**: → `#report-event(event: "precheckCompleted", phase: "precheck", status: "failed", details: {category: "", scenario: ""}, message: "")` — **Call this FIRST** before stopping or asking users. Pass the failed category (e.g., "Unsupported Project", "Invalid Goal") and scenario from the table above. **IMPORTANT**: `details.category` and `details.scenario` are **REQUIRED** when status is "failed" — the tool will reject the call without them. **Exception**: For the "Missing target version" scenario, do NOT report failure immediately — interact with the user first (see table above) and only report `precheckCompleted` (succeeded or failed) after the user has selected a target or the interaction concludes. - -**On success**: → `#report-event(event: "precheckCompleted", phase: "precheck", status: "succeeded", details: {baseJdkVersion: "", targetVersion: ""})` — **This generates a new `SESSION_ID`. Use this `SESSION_ID` for all subsequent tool calls.** - -### Phase 2: Generate Upgrade Plan - -#### 1. Initialize & Analyze - -1. Call tool `#report-event(sessionId, event: "planGenerationStarted", phase: "plan", status: "succeeded")` — **FIRST action, before any file or version control operations** -2. **Detect version control availability**: Use `#version-control(sessionId: , workspacePath, action: "checkStatus")` to detect if git is available. If the response indicates version control is unavailable, set `GIT_AVAILABLE=false`. **Do not ask the user. Do not report failure.** -3. If `GIT_AVAILABLE=true`: Use `#version-control(sessionId: , workspacePath, action: "stashChanges", stashMessage: "java-upgrade-precheck-")` to stash any uncommitted changes. -4. **Project environment**: Extract user-specified guidelines. Detect all available JDKs/build tools via `#list-jdks(sessionId)`, `#list-mavens(sessionId)`. Detect wrapper presence and read wrapper properties if present. Check build tool version compatibility with target JDK — flag incompatible versions. -5. **Technology stack analysis**: Identify core tech stack across **ALL modules** — direct deps, upgrade-critical transitive deps, build tools, and build plugins (`maven-compiler-plugin`, `maven-surefire-plugin`, `maven-war-plugin`, etc.). Flag EOL dependencies. Determine compatibility against upgrade goals. -6. **Compatibility scan**: Perform a comprehensive scan for all upgrade-blocking patterns. - - **What to find:** - - | Dimension | What to look for | - |-----------|-----------------| - | JDK source compatibility | Reflection into java.base internals (`setAccessible` on JDK classes), internal package imports (`sun.misc.*`, `sun.reflect.*`, `jdk.internal.*`), removed/deprecated APIs for the target JDK version, JDK-removed modules needing explicit deps (JAXB, javax.activation, etc.) | - | Framework breaking changes | For each framework major version jump: fetch the official migration guide, extract all breaking change patterns (removed/renamed classes, changed package namespaces, removed config properties, deprecated-then-removed APIs, changed defaults), then search source code and config files for every pattern found | - | CI/CD configuration | All CI/CD files (Dockerfile, workflows, pipelines, Jenkinsfile, etc.) with hardcoded JDK/Java version references that need updating | - | Build plugin compatibility | Build plugins incompatible with the target JDK version | - | Dependency version conflicts | Explicit version pins/overrides in dependency management that conflict with the target framework's BOM | - | Test infrastructure | Test framework compatibility with target JDK/framework (JUnit 4→5 migration, Mockito version compatibility, Spring Test API changes, test utility class removals/renames) | - | Configuration properties | Renamed, removed, or restructured config properties in application.properties/application.yml (e.g., Spring Boot 2→3 renames like `spring.redis.*` → `spring.data.redis.*`, removed properties, changed defaults) | - | Runtime behavior changes | JDK runtime behavior differences that compile but fail at runtime: serialization format changes, default locale/charset changes, `HashMap`/`HashSet` iteration order assumptions, `SecurityManager` removal (JDK 17+), strong encapsulation of internal APIs | - | Resource/metadata files | Framework metadata files that changed format or location (e.g., Spring Boot 3: `META-INF/spring.factories` → `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`), `META-INF/services` entries, `persistence.xml`, `web.xml`, and other descriptor files | - - **Output requirement**: For each finding, record: file, line/location, current state, what needs to change, and why. Every finding must appear in the plan's Impact Analysis section. No known findings may be deferred to execution. Document known unknowns (e.g., transitive dependency conflicts only discoverable after version changes, runtime-only reflection issues) in Risks & Warnings with mitigation strategies. - -#### 2. Design & Review - -1. For incompatible deps in the Technology Stack, prefer: Replacement > Adaptation > Rewrite -2. Determine intermediate versions needed (see **Intermediate Version Strategy**) -3. Finalize Available Tools based on the planned step sequence; determine which JDK versions are required and at which steps; mark missing ones as ``, mark build tools needing upgrade as `` (including wrapper version if applicable). **Exception — base (current) JDK**: If the project's current JDK version is not found via `#list-jdks`, do **not** mark it as ``. The base JDK is only needed for the optional baseline step. Instead, note it as "not available (baseline will be skipped)". -4. Design upgrade steps — group related changes so each step leaves the project in a compilable state. No step should expect compilation failure. Reference Impact Analysis items rather than repeating details. -5. **Self-verify completeness**: Every finding from the Compatibility Scan must appear in the Impact Analysis section. Every item in Impact Analysis must be addressed by an Upgrade Step. If gaps are found, go back and fill them. -6. **Write complete `plan.md`** to `.github/java-upgrade//plan.md` using `create_file` — follow the **Plan Format Specification** above. Include all sections (Available Tools, Guidelines, Options, Upgrade Goals, Technology Stack, Derived Upgrades, Impact Analysis, Upgrade Steps) in a single write. If `GIT_AVAILABLE=false`, use "N/A" for branch/commit and include a notice about version control. -7. Verify all placeholders are filled, check for missing coverage/infeasibility/limitations. If issues found, rewrite the file. -8. Call tool `#report-event(sessionId, event: "planReviewed", phase: "plan", status: "succeeded")` - -### Phase 3: Confirm Plan with User (MANDATORY) - -1. Call tool `#confirm-upgrade-plan(sessionId)` — awaits user confirmation - -### Phase 4: Execute Upgrade Plan - -#### 1. Initialize - -1. Read `.github/java-upgrade//plan.md` for "Options" -2. Use `#version-control(sessionId: , workspacePath, action: "stashChanges")` to stash any uncommitted changes. Then use `#version-control(sessionId: , workspacePath, action: "createBranch", branchName: "appmod/java-upgrade-")` (or the branch defined in `plan.md`). If version control is unavailable (`GIT_AVAILABLE=false`), log warning in `plan.md` that changes are not version-controlled. -3. Update `.github/java-upgrade//progress.md`: - - Replace ``, `` and timestamp placeholders - - Create step entries for each step in `plan.md` (per **Template compliance** rule) -4. Call tool `#report-event(sessionId, event: "planExecutionStarted", phase: "execute", status: "succeeded")` - -#### 2. Execute: - -For each step: - -1. Read `.github/java-upgrade//plan.md` for step details and guidelines -2. Mark ⏳ in `.github/java-upgrade//progress.md` -3. Make changes as planned (use OpenRewrite if helpful, verify results) - - Add TODOs for any deferred work, e.g., temporary workarounds -4. **Review Code Changes** (per rules in `progress.md` template): Verify sufficiency (all required changes present) and necessity (no unnecessary changes, functional behavior preserved, security controls maintained). - - Add missing changes and revert unnecessary changes. Document any unavoidable behavior changes with justification. -5. Verify with specified command/JDK - - **Steps 1-N (Setup/Upgrade)**: Compilation must pass (including both main and test code, fix immediately if not). Test failures acceptable - document count. - - **Final Validation Step**: Achieve **Upgrade Success Criteria** - iterative test & fix loop until 100% pass (or ≥ baseline). NO deferring. **Skip test execution if "Run tests before and after the upgrade: false" in plan.md Options — only verify compilation in that case.** - - After each build (`mvn clean test-compile` or equivalent): `#report-event(sessionId, event: "buildCompleted", phase: "execute", status: "succeeded"|"failed")` - - After each test run (`mvn clean test` or equivalent): `#report-event(sessionId, event: "testCompleted", phase: "execute", status: "succeeded"|"failed")` -6. Commit using `#version-control(sessionId: , workspacePath, action: "commitChanges")` (if version control available; otherwise, log details in `progress.md`): - - commitMessage format — First line: `Step : - Compile: <result>` or `Step <x>: <title> - Compile: <result>, Tests: <pass>/<total> passed` (if tests run) - - Body: Changes summary + concise known issues/limitations (≤5 lines) - - **Security note**: If any security-related changes were made, include "Security: <change description and justification>" -7. Update `progress.md` with step details and mark ✅ or ❗ -8. Report event at end of each step: - - **Step 1 (Setup Environment)**: `#report-event(sessionId, event: "environmentSetup", phase: "execute", status: "succeeded"|"failed"|"skipped", details: {jdkPath: "<JDK path>", buildToolPath: "<build tool executable path>"})` — **details are REQUIRED** for this event. The `jdkPath` and `buildToolPath` must be valid paths that exist on this machine. Use `"."` for `buildToolPath` if a wrapper (mvnw/gradlew) is used. - - **Step 2 (Setup Baseline)**: `#report-event(sessionId, event: "baselineSetup", phase: "execute", status: "succeeded"|"failed"|"skipped")` — use `"skipped"` with a `message` when the base JDK is not available - - **Before each upgrade step (Steps 3-N)**: `#report-event(sessionId, event: "upgradeStepStarted", phase: "execute", status: "succeeded", details: {stepNumber: <N>, stepTitle: "<title>"})` - - **After each upgrade step (Steps 3-N)**: `#report-event(sessionId, event: "upgradeStepCompleted", phase: "execute", status: "succeeded"|"failed", details: {stepNumber: <N>, stepTitle: "<title>", commitId: "<commitId from #version-control response, or 'N/A' if version control unavailable>"})` - - **Final step (Final Validation)**: `#report-event(sessionId, event: "upgradeValidationCompleted", phase: "execute", status: "succeeded"|"failed", details: {stepNumber: <N>, stepTitle: "<title>", commitId: "<commit_id from #version-control response if version control available, otherwise 'N/A'>"})` - -#### 3. Complete - -1. Validate all steps in `plan.md` have ✅ in `.github/java-upgrade/<SESSION_ID>/progress.md` -2. Validate all **Upgrade Success Criteria** are met, or otherwise go back to Final Validation step to fix -3. Call tool `#report-event(sessionId, event: "planExecutionCompleted", phase: "execute", status: "succeeded")` - -### Phase 5: Summarize & Cleanup - -1. **Scan CVEs**: Extract direct deps (`mvn dependency:list -DexcludeTransitive=true`), call `#validate-cves-for-java(sessionId, dependencies, projectPath)` -2. **Collect test coverage**: Run `mvn clean verify -Djacoco.skip=false` or equivalent; record metrics -3. Generate `summary.md`: - - **Read spec**: Read `summary.template.md` (in the session directory) — it contains the format specification with rules and samples. - - **Write**: Collect all data from `progress.md`, build output, CVE scan results, and coverage metrics. Resolve OS username (`$env:USERNAME` / `$USER` / `whoami`). Write `summary.md` as a new file using `create_file` per **Template compliance**. - - **Self-check**: Scan the written `summary.md` for HTML comments, `<placeholder>` tokens, empty bullets, unfilled table cells, bare headings without content, duplicate section headings. Fix any issues found. -4. Clean up temp files; remove HTML comments from all `.md` files -5. → `#report-event(sessionId, event: "summaryGenerated", phase: "summarize", status: "succeeded", message: "<1-2 sentence summary>")` - -### Phase 6: Prompt for Follow-up Actions (CONDITIONAL) - -If issues detected, use `#askQuestions` to prompt user: - -1. **Critical/High CVEs found**: Offer to upgrade vulnerable dependencies using this custom agent; use `#validate-cves-for-java(sessionId)` to verify resolution. -2. **Low coverage (<70%)**: Offer to generate tests via `#generate-tests-for-java(sessionId, projectPath)`. - - ---- - -## Working Folder and Reporting - -When invoked from the appmod CLI orchestrator, the calling prompt will provide a -`modernization-work-folder` and a `TaskId`. The following rules are MANDATORY and -override any conflicting working-folder convention from the sections above: - -- Use `${modernization-work-folder}` as the working directory for ALL bookkeeping - artifacts (plan notes, progress logs, intermediate results). Do NOT create or - write into any folder outside `${modernization-work-folder}` for these artifacts - (e.g., do NOT use `.github/java-upgrade/<timestamp>/` or any other ad-hoc - location). Source-code edits inside the target repository are unaffected by - this rule. - -- Before returning, you MUST: - 1. Create `${modernization-work-folder}/${TaskId}/` if it does not exist. - 2. Write `${modernization-work-folder}/${TaskId}/modernization-summary.md` with: - - `finalStatus`: one of `"success"`, `"failed"`, `"skipped"` - - `successCriteriaStatus`: object with boolean `passBuild`, - `generateNewUnitTests`, `passUnitTests` - - `summary`: short prose summary of what changed - - `failureReason`: short prose, only when `finalStatus` is `"failed"` - 3. Return the same `finalStatus` / `successCriteriaStatus` / `summary` in your - final message so the orchestrator can update `tasks.json`. diff --git a/.github/skills/execute-modernization-task/SKILL.md b/.github/skills/execute-modernization-task/SKILL.md deleted file mode 100644 index 09bb4dd07..000000000 --- a/.github/skills/execute-modernization-task/SKILL.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -name: execute-modernization-task -description: Execute a modernization task as part of a modernization plan ---- -# Role -You are a code migration agent that executes modernization tasks. You will change the code according to skills, migration requirement, environment configuration and success criteria - -# Principles -1) Reuse current branch when to do the code change -2) NEVER discard any change -3) If a relevant skill exists in the available skills list, load it for more information about the task. - -# Reading Provisioned Resources - -If `./infra/infra-config.md` exists, it contains information about Azure resources provisioned by an infrastructure task. This information can help you use correct resource names and endpoints in configuration templates. - -# Workflow -Follow these steps in order when executing a modernization task: - -1. **Extract Knowledge Base**: Load all relevant skills from the available skills list. Extract the best practices and migration guidance they contain. This knowledge base takes precedence over any general knowledge you have. -2. **Analyze and Migrate**: Analyze the current code and reason about each required code change based on the extracted knowledge base. When there is a conflict between your general knowledge and the skill-provided best practices, always follow the skill-provided best practices. -3. **Consistency Check**: After completing code migration, run the consistency check. -4. **Build and Test**: Build the source and run unit tests. The source must be buildable and no new test failures may be introduced by your changes. -5. **Re-verify After Any Change**: Every time you make a change — including consistency fixes — you must rebuild and re-run unit tests, even if the previous build and test run were successful. - -# Consistency Check - -Call custom agent `task` with the following prompt to run the consistency check: - - ```md - Call skill validation-check-consistency to validate the consistency of the migrated code. - - modernization-work-folder: ${modernization-work-folder} - - task-id: ${taskid} - - task-skill: The skill(s) used for this migration task, you should find it in ${modernization-work-folder}/.metadata/tasks.json - ``` - -Review the consistency check results. If any Critical or Major issues are found, fix them and re-run the consistency check. Repeat this fix-and-revalidate loop until the check reports zero Critical and zero Major issues before proceeding. - -# Exit Criteria -Before committing and marking the task as complete, verify: -1. **Consistency**: Fix all Critical and Major issues. Apply best-effort fixes for Minor issues. -2. **Completeness**: All old technology references relevant to this task are fully removed or replaced — check source files, configuration files, build files, and test files; do not leave partial old-technology remnants -3. **Build and tests**: If the task success criteria require `passBuild` or `passUnitTests`, confirm they pass before finishing - -Do not mark the task as complete until all applicable exit criteria are satisfied. - -# Final Check - -Run a full build and execute all unit tests one last time. Confirm there are no build errors and no unit test failures before proceeding to commit. - -# Output -1) Create a subfolder ${taskid} under ${modernization-work-folder}. You only need to generate a summary report "modernization-summary.md", under this subfolder to summarize the changes, and there is no need to generate any other documents. -2) Make a commit when the task is completed with the changes made in the modernization task. diff --git a/.github/skills/execute-upgrade-task/SKILL.md b/.github/skills/execute-upgrade-task/SKILL.md deleted file mode 100644 index e4b7bb633..000000000 --- a/.github/skills/execute-upgrade-task/SKILL.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: execute-upgrade-task -description: Execute an upgrade task as part of a modernization plan ---- -# Role -You are a code migration agent that executes upgrade tasks. You will change the code according to skills, migration requirement, environment configuration and success criteria - -# Principles -1) Reuse current branch when to do the code change -2) NEVER discard any change -3) If a relevant skill exists in the available skills list, load it for more information about the task. - -# Exit Criteria -Before committing and marking the task as complete, verify: -1. **Consistency**: All upgrade goals described in the task are correctly and completely implemented — re-read the task description and requirements and confirm every goal is addressed in the changed files -2. **Completeness**: All old technology references relevant to this task are fully removed or replaced — check source files, configuration files, build files, and test files; do not leave partial old-technology remnants -3. **Build and tests**: If the task success criteria require `passBuild` or `passUnitTests`, confirm they pass before finishing - -Do not mark the task as complete until all applicable exit criteria are satisfied. - -# Output -1) Create a subfolder ${taskid} under ${modernization-work-folder}. You only need to generate a summary report "modernization-summary.md", under this subfolder to summarize the changes, and there is no need to generate any other documents. -2) Make a commit when the task is completed with the changes made in the upgrade task. diff --git a/.github/skills/fact-application-name/SKILL.md b/.github/skills/fact-application-name/SKILL.md deleted file mode 100644 index ec12cb292..000000000 --- a/.github/skills/fact-application-name/SKILL.md +++ /dev/null @@ -1,112 +0,0 @@ ---- -name: fact-application-name -description: Identify application name/identifier from configuration files ---- - -# Application Name Analysis - -## Purpose -Extract the application name or identifier from project files, configuration, and build descriptors. - -## Target Files/Locations -- **/pom.xml (<name>, <artifactId>) -- **/*.csproj (<AssemblyName>, <RootNamespace>) -- **/package.json (name field) -- **/build.gradle (rootProject.name) -- **/application.{properties,yml} (spring.application.name) -- **/README.md (title or project name) - -## Example Patterns -- `<name>CustomerPortal</name>` (Maven) -- `"name": "order-processor"` (Node.js) -- `spring.application.name=payment-service` -- `<AssemblyName>InventoryAPI</AssemblyName>` (.NET) - -## Analysis Steps - -### 1. Check Build File Names -``` -Maven (pom.xml): -Use Grep: "<name>|<artifactId>" -Extract: <name>ProjectName</name> - -Gradle (build.gradle): -Use Grep: "rootProject\\.name" -Extract: rootProject.name = 'project-name' - -Node.js (package.json): -Use Read and parse JSON: { "name": "app-name" } - -.NET (*.csproj): -Use Grep: "<AssemblyName>|<RootNamespace>" -``` - -### 2. Check Application Configuration -``` -Use Grep: "spring\\.application\\.name|app\\.name|application\\.name" -Files: **/application.{properties,yml}, **/appsettings.json -Context: -B 1 -A 1 -``` - -### 3. Check README -``` -Use Read: **/README.md (first 50 lines) -Look for: -- # Title -- Project name in first paragraph -- Badge labels -``` - -### 4. Check Container Names -``` -Use Grep: "container_name:" -Files: **/docker-compose*.yml -Application name often in container name -``` - -## Confidence Determination - -### High Confidence -- ✅ Name consistently appears across multiple files -- ✅ Clear identifier in build/config files -- **Example**: "Application name: CustomerPortal from pom.xml, spring.application.name, and README" - -### Medium Confidence -- ⚠️ Name in build file but not elsewhere -- ⚠️ Generic name (app, service, api) -- **Example**: "Project name: my-app (generic name from package.json only)" - -### Low Confidence -- ⚠️ No clear application name -- ⚠️ Names inconsistent across files -- **Example**: "Multiple names found, primary name unclear" - -### Not Applicable -- ❌ Multi-module project with multiple applications -- **Example**: "Monorepo with 10 microservices, no single application name" - -## Output Format - -```json -{ - "input_name": "Application Name", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Application name}", - "confidence": "high|medium|low", - "evidence": [ - "{Source file and field}", - "{Consistency across files}", - "{Alternative names found}" - ], - "values": [ - "{Primary name}", - "{Alternative names/identifiers}", - "{Artifact IDs or namespaces}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-application-port/SKILL.md b/.github/skills/fact-application-port/SKILL.md deleted file mode 100644 index df9d5e3d7..000000000 --- a/.github/skills/fact-application-port/SKILL.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -name: fact-application-port -description: Identify exposed application ports from container configuration ---- - -# Application Port Analysis - -## Purpose -Identify which ports the containerized application exposes for HTTP, HTTPS, and other services. - -## Target Files/Locations -- **/Dockerfile (EXPOSE instruction) -- **/docker-compose*.yml (ports section) -- **/k8s/**/*.yaml (containerPort, servicePort) -- **/application.{properties,yml} (server.port) - -## Example Patterns -- `EXPOSE 8080 443` -- `ports: - "3000:3000"` -- `containerPort: 8080` -- `server.port=8080` - -## Analysis Steps - -### 1. Check Dockerfile EXPOSE -``` -Use Grep: "^EXPOSE\\s+" -Files: **/Dockerfile, **/Containerfile -Extract port numbers -``` - -### 2. Analyze docker-compose Ports -``` -Use Read: **/docker-compose*.yml -Look for ports: section -Format: "HOST:CONTAINER" or just "PORT" -``` - -### 3. Check Kubernetes Manifests -``` -Use Grep: "containerPort|servicePort|targetPort" -Files: **/k8s/**/*.yaml, **/*.yaml -Context: -B 2 -A 2 -``` - -### 4. Check Application Configuration -``` -Use Grep: "server\\.port|PORT|HTTP_PORT" -Files: **/application.{properties,yml}, **/.env -``` - -## Confidence Determination - -### High Confidence -- ✅ EXPOSE in Dockerfile + ports in compose/k8s -- ✅ Application config matches container config -- **Example**: "Application exposes port 8080 (HTTP) and 443 (HTTPS) based on Dockerfile EXPOSE and docker-compose ports" - -### Medium Confidence -- ⚠️ Ports in config but not all files consistent -- **Example**: "Port 8080 in Dockerfile EXPOSE, but docker-compose uses 3000:8080" - -### Low Confidence -- ⚠️ No explicit port configuration -- **Example**: "No EXPOSE instruction, likely uses default runtime port" - -### Not Applicable -- ❌ No container or non-networked app -- **Example**: "CLI application, no network ports" - -## Output Format - -```json -{ - "input_name": "Application Port", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Ports summary}", - "confidence": "high|medium|low", - "evidence": [ - "{Dockerfile EXPOSE}", - "{docker-compose ports}", - "{K8s port config}", - "{Application config}" - ], - "values": [ - "{Port numbers: 8080, 443, etc.}", - "{Protocol: HTTP, HTTPS, TCP}", - "{Port mapping if applicable}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-application-type/SKILL.md b/.github/skills/fact-application-type/SKILL.md deleted file mode 100644 index a171d88df..000000000 --- a/.github/skills/fact-application-type/SKILL.md +++ /dev/null @@ -1,133 +0,0 @@ ---- -name: fact-application-type -description: Determine the type of application (Web App, API, Service, etc.) ---- - -# Application Type Analysis - -## Purpose -Identify the type of application based on code structure and dependencies. - -## Target Files/Locations -- **/pom.xml, **/build.gradle, **/build.gradle.kts (Java) -- **/*.csproj (C#/.NET) -- **/package.json (Node.js) -- **/requirements.txt, **/Pipfile, **/pyproject.toml (Python) -- **/*.java, **/*.cs, **/*.js, **/*.ts, **/*.py (source code) - -## Analysis Steps - -### 1. Check Java Web Frameworks -``` -Use Grep: "spring-boot-starter-web|@RestController|@RequestMapping|spring-boot-starter-webflux" -Files: **/pom.xml, **/build.gradle, **/*.java - -Map findings: -- spring-boot-starter-web / @RestController → REST API -- spring-boot-starter-webflux → REST API (Reactive) -``` - -### 2. Check Java gRPC -``` -Use Grep: "grpc|io\\.grpc" -Files: **/pom.xml, **/build.gradle, **/*.proto - -If found → gRPC Service -``` - -### 3. Check .NET Project Type -``` -Use Grep: "Microsoft\\.AspNetCore|Microsoft\\.NET\\.Sdk\\.Web" -Files: **/*.csproj - -Map findings: -- Microsoft.NET.Sdk.Web / Microsoft.AspNetCore → Web App / REST API -``` - -### 4. Check .NET gRPC -``` -Use Grep: "Grpc\\.AspNetCore" -Files: **/*.csproj - -If found → gRPC Service -``` - -### 5. Check .NET Background Service -``` -Use Grep: "BackgroundService|Microsoft\\.Extensions\\.Hosting" -Files: **/*.csproj, **/*.cs - -If found (and no web SDK) → Background Service -``` - -### 6. Check Node.js Frameworks -``` -Use Grep: "express|fastify|koa|@nestjs|@grpc/grpc-js" -Files: **/package.json - -Map findings: -- express / fastify / koa / @nestjs → REST API / Web App -- @grpc/grpc-js → gRPC Service -``` - -### 7. Check Python Frameworks -``` -Use Grep: "flask|django|fastapi|tornado|grpcio" -Files: **/requirements.txt, **/Pipfile, **/pyproject.toml - -Map findings: -- flask / django / fastapi / tornado → REST API / Web App -- grpcio → gRPC Service -``` - -### 8. Check for Batch/Job Indicators -``` -Use Glob: **/cron*, **/*job*, **/*batch*, **/*scheduler* -Use Grep: "@Scheduled|CronJob|BackgroundJob" -Files: **/*.{java,cs,py} - -If no web framework found but batch patterns detected → Batch Job -``` - -## Confidence Determination - -### High Confidence -- ✅ Web framework dependency clearly identified -- ✅ REST/gRPC annotations in source code -- **Example**: "REST API: Spring Boot with @RestController annotations" - -### Medium Confidence -- ⚠️ Framework found but type ambiguous -- **Example**: "ASP.NET Core project, could be Web App or API" - -### Low Confidence -- ⚠️ No clear framework indicators -- **Example**: "Unable to determine application type from available files" - -### Not Applicable -- ❌ Library project with no entry point -- **Example**: "Library/SDK project, no application type" - -## Output Format - -```json -{ - "input_name": "Application Type", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Application type}", - "confidence": "high|medium|low", - "evidence": [ - "{Framework dependencies}", - "{Annotations/decorators found}", - "{Project structure indicators}" - ], - "values": [ - "{Type: REST API, Web App, gRPC Service, Background Service, Batch Job, etc.}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-architecture-pattern/SKILL.md b/.github/skills/fact-architecture-pattern/SKILL.md deleted file mode 100644 index cd3c5e80a..000000000 --- a/.github/skills/fact-architecture-pattern/SKILL.md +++ /dev/null @@ -1,218 +0,0 @@ ---- -name: fact-architecture-pattern -description: Identify the application architecture pattern ---- - -# Architecture Pattern Analysis - -## Purpose -Determine the architectural pattern used in the application: Monolith, Microservices, Layered, Event-Driven, MVC, etc. - -## Analysis Strategy - -This SKILL performs a comprehensive analysis of project structure, dependencies, and code patterns to identify the architecture. - -## Analysis Steps - -### 1. Project Structure Analysis - -**Check for Microservices Indicators:** -```bash -# Use Glob to search for service directories -**/services/**/ -**/microservices/**/ -**/apps/**/ -``` - -**Check for Monolith Indicators:** -- Single deployable unit -- All code in one project/module - -### 2. Configuration & Infrastructure Analysis - -**Search for Service Discovery (Microservices):** -```bash -# Use Grep to find service discovery patterns -Pattern: "eureka|consul|etcd|kubernetes|k8s|service\.discovery" -Files: **/*.{yaml,yml,json,xml,properties,config} -``` - -**Search for API Gateway (Microservices):** -```bash -Pattern: "zuul|gateway|kong|ambassador|istio|envoy|traefik" -Files: **/*.{yaml,yml,json,xml,properties,pom.xml,build.gradle,package.json} -``` - -**Search for Message Brokers (Event-Driven):** -```bash -Pattern: "kafka|rabbitmq|activemq|azure\.servicebus|aws\.sqs|redis\.pub" -Files: **/*.{yaml,yml,json,xml,properties,config} -``` - -### 3. Code Pattern Analysis - -**MVC Pattern Detection:** -```bash -# Search for controllers -Pattern: "@Controller|@RestController|ApiController|[Controller]|class.*Controller" -Files: **/*.{java,cs,py,js,ts} - -# Search for models -Glob: **/models/**/*.*, **/entities/**/*.*, **/domain/**/*.* - -# Search for views -Glob: **/views/**/*.*, **/templates/**/*.* -``` - -**Layered Architecture Detection:** -```bash -# Search for layer separation -Glob: **/controller*/**/*, **/service*/**/*, **/repository*/**/*, **/dao*/**/*.* - -Pattern: "@Service|@Repository|@Component|[Service]|interface.*Repository" -Files: **/*.{java,cs} -``` - -**Event-Driven Pattern Detection:** -```bash -Pattern: "@EventHandler|@KafkaListener|@RabbitListener|EventEmitter|publish\(|subscribe\(" -Files: **/*.{java,cs,js,ts,py} -``` - -### 4. Deployment Configuration Analysis - -**Container Orchestration (Microservices):** -```bash -Glob: **/kubernetes/**/*.yaml, **/k8s/**/*.yaml, **/helm/**/*.* -Glob: docker-compose*.{yml,yaml} -``` - -**Check docker-compose for multiple services:** -```bash -Pattern: "services:\n(.*\n){3,}" -Files: docker-compose*.{yml,yaml} -``` - -## Analysis Decision Tree - -1. **If multiple deployable services detected** → Microservices Architecture - - Evidence: Multiple service directories, service discovery, API gateway - -2. **If message brokers + event handlers detected** → Event-Driven Architecture - - Evidence: Kafka/RabbitMQ configs, event listeners, pub/sub patterns - -3. **If clear MVC structure detected** → MVC Architecture - - Evidence: Controllers, Models, Views directories - -4. **If layer separation detected (but single deployment)** → Layered Monolith - - Evidence: Controller/Service/Repository layers in single project - -5. **If single project with no clear separation** → Simple Monolith - - Evidence: All code in one directory, minimal structure - -## Confidence Levels - -- **High**: Clear indicators from multiple sources (structure + config + code patterns) -- **Medium**: Some indicators present but mixed signals -- **Low**: Limited evidence, manual inspection recommended - -## Output Format - -After analysis, call the MCP tool: - -```javascript -write_assessment_result({ - resultJson: JSON.stringify({ - input_name: "Architecture Pattern", - analysis_method: "Hybrid", // Code + LLM analysis - status: "success", // or "not_applicable" or "failed" - result: { - finding: "Microservices Architecture", - confidence: "high", // high, medium, or low - evidence: [ - "Found 5 service directories: user-service, order-service, payment-service, notification-service, gateway-service", - "docker-compose.yml defines 5 separate services", - "Found Eureka service discovery configuration", - "API Gateway detected: Zuul configuration in gateway-service/application.yml" - ], - values: ["Microservices", "Service Discovery", "API Gateway"], - architecture_details: { - pattern: "Microservices", - service_count: 5, - has_service_discovery: true, - has_api_gateway: true, - has_message_broker: false - } - }, - execution_time_seconds: 3.5, - timestamp: new Date().toISOString() - }), - assessmentDir: variables.assessment_dir -}); -``` - -## Example Patterns by Technology Stack - -### Java/Spring Boot - -**Microservices:** -- `@EnableEurekaClient`, `@EnableDiscoveryClient` -- `spring-cloud-starter-netflix-eureka` -- Multiple `@SpringBootApplication` classes - -**Layered Monolith:** -- Single `@SpringBootApplication` -- Packages: `controller`, `service`, `repository`, `entity` - -### .NET/ASP.NET Core - -**Microservices:** -- Multiple `.csproj` files with `<OutputType>Exe</OutputType>` -- `Microsoft.Extensions.Http.Polly` (service-to-service calls) -- `Steeltoe.Discovery.Eureka` - -**MVC:** -- Single `.csproj` with `Microsoft.AspNetCore.Mvc` -- Folders: `Controllers`, `Models`, `Views` - -### Node.js/Express - -**Microservices:** -- Multiple `package.json` files in subdirectories -- `express-gateway`, `consul` dependencies - -**Layered:** -- Single `package.json` -- Folders: `routes`, `controllers`, `services`, `models` - -### Python - -**Microservices:** -- Multiple `app.py` or `main.py` files -- `nameko`, `flask-consul` dependencies - -**Event-Driven:** -- `kafka-python`, `pika` (RabbitMQ), `celery` -- Event handler decorators - -## Not Applicable Scenarios - -Return `status: "not_applicable"` if: -- Project is not an application (library, CLI tool only) -- Insufficient code to determine architecture -- Non-standard structure that doesn't fit patterns - -## Error Handling - -Return `status: "failed"` if: -- Unable to access project files -- Critical analysis error occurred - -Include error details in the result. - -## Notes - -- Architecture can be hybrid (e.g., Layered + Event-Driven) -- Include all applicable patterns in `values` array -- Prioritize most dominant pattern in `finding` -- If uncertain between patterns, use `confidence: "medium"` and explain in evidence diff --git a/.github/skills/fact-base-image/SKILL.md b/.github/skills/fact-base-image/SKILL.md deleted file mode 100644 index f48912217..000000000 --- a/.github/skills/fact-base-image/SKILL.md +++ /dev/null @@ -1,102 +0,0 @@ ---- -name: fact-base-image -description: Identify container base image used in Dockerfile ---- - -# Base Image Analysis - -## Purpose -Identify the base container image (e.g., alpine:3.15, ubuntu:20.04, node:16-alpine) used in Dockerfile/Containerfile to understand OS dependencies and image foundations. - -## Target Files/Locations -- **/Dockerfile, **/Containerfile -- **/docker-compose*.yml -- **/k8s/**/*.yaml - -## Example Patterns -- `FROM alpine:3.15` -- `FROM ubuntu:20.04` -- `FROM node:16-alpine AS builder` -- `FROM mcr.microsoft.com/dotnet/sdk:6.0` - -## Analysis Steps - -### 1. Find and Read Dockerfiles -``` -Use Glob: **/Dockerfile, **/Containerfile -Use Read to examine each file -Look for FROM instructions -``` - -### 2. Extract Base Images -``` -Use Grep: "^FROM\\s+" -Files: **/Dockerfile, **/Containerfile -Context: -A 1 - -Parse format: FROM <image>:<tag> [AS <stage>] -``` - -### 3. Check for Multi-stage Builds -``` -Multiple FROM instructions indicate multi-stage build -Identify final stage base image -Note all intermediate base images -``` - -### 4. Verify Image Registry -``` -Check for: -- Docker Hub (no prefix or docker.io/) -- MCR (mcr.microsoft.com/) -- GCR (gcr.io/) -- ECR (*.amazonaws.com/) -- Custom registry -``` - -## Confidence Determination - -### High Confidence -- ✅ Dockerfile exists with explicit FROM instruction -- ✅ Image name and tag clearly specified -- **Example**: "Base image: alpine:3.15 from Dockerfile line 1" - -### Medium Confidence -- ⚠️ Multiple stages with different base images -- ⚠️ FROM uses build argument (FROM ${BASE_IMAGE}) -- **Example**: "Base image uses variable, likely node:16 based on docker-compose" - -### Low Confidence -- ⚠️ No Dockerfile found -- ⚠️ Base image unclear from manifests -- **Example**: "Kubernetes deployment exists but base image not specified" - -### Not Applicable -- ❌ No containerization -- **Example**: "No container files found" - -## Output Format - -```json -{ - "input_name": "Base Image", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Base image identified}", - "confidence": "high|medium|low", - "evidence": [ - "{Dockerfile location}", - "{FROM instruction(s)}", - "{Multi-stage details if applicable}" - ], - "values": [ - "{Base image name:tag}", - "{OS type (Alpine, Ubuntu, etc.)}", - "{Registry source}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-communication-protocols/SKILL.md b/.github/skills/fact-communication-protocols/SKILL.md deleted file mode 100644 index 6b6a9082f..000000000 --- a/.github/skills/fact-communication-protocols/SKILL.md +++ /dev/null @@ -1,118 +0,0 @@ ---- -name: fact-communication-protocols -description: Identify inter-service communication protocols (HTTP/REST, gRPC, Message Queue, TCP) ---- - -# Communication Protocols Analysis - -## Purpose -Detect communication protocols used for inter-service communication, API exposure, and external integrations. - -## Target Files/Locations -- **/pom.xml, **/build.gradle, **/*.csproj, **/package.json (dependencies) -- **/*.proto (gRPC definitions) -- **/application.{properties,yml} (message queue configs) -- **/*.java, **/*.cs, **/*.js (HTTP clients, gRPC stubs) - -## Example Patterns -- **HTTP/REST**: RestTemplate, HttpClient, axios, fetch, @RestController -- **gRPC**: grpc-*, *.proto files, gRPC stubs -- **Message Queues**: RabbitMQ, Kafka, Redis Pub/Sub, AWS SQS -- **TCP/UDP**: Socket, ServerSocket, TcpClient -- **WebSocket**: ws://, WebSocket, SignalR - -## Analysis Steps - -### 1. Check for HTTP/REST -``` -Use Grep: "@RestController|@RequestMapping|HttpClient|RestTemplate|axios|fetch" -Files: **/*.{java,cs,js,py} -Context: -B 2 -A 2 - -Check Spring: @RestController, @GetMapping -Check .NET: HttpClient, [ApiController] -Check Node: express, axios -``` - -### 2. Check for gRPC -``` -Use Glob: **/*.proto -Count proto files - -Use Grep: "grpc|GrpcClient|GrpcChannel" -Files: **/*.{java,cs,go,py} -Dependencies: grpc-netty, Grpc.Net.Client, @grpc/grpc-js -``` - -### 3. Check for Message Queues -``` -Use Grep: "RabbitTemplate|KafkaTemplate|@JmsListener|IMessageQueue" -Files: **/*.{java,cs} - -Dependencies: -- spring-boot-starter-amqp (RabbitMQ) -- spring-kafka (Kafka) -- MassTransit, NServiceBus (.NET) - -Config: -- spring.rabbitmq.*, spring.kafka.* -``` - -### 4. Check for WebSocket -``` -Use Grep: "WebSocket|ws://|wss://|SignalR|@ServerEndpoint" -Files: **/*.{java,cs,js} -``` - -### 5. Check for TCP/UDP -``` -Use Grep: "ServerSocket|Socket|TcpListener|UdpClient" -Files: **/*.{java,cs,go} -``` - -## Confidence Determination - -### High Confidence -- ✅ Multiple protocol indicators found -- ✅ Dependencies + code usage confirmed -- **Example**: "HTTP/REST via Spring MVC, gRPC for internal services (5 .proto files), Kafka for async messaging" - -### Medium Confidence -- ⚠️ Dependencies present but usage unclear -- **Example**: "HTTP REST API likely, gRPC dependency but no .proto files found" - -### Low Confidence -- ⚠️ Protocol inferred from framework -- **Example**: "Likely HTTP-based (web framework) but protocol not explicit" - -### Not Applicable -- ❌ Standalone application with no communication -- **Example**: "CLI tool with no network communication" - -## Output Format - -```json -{ - "input_name": "Communication Protocols", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Protocols summary}", - "confidence": "high|medium|low", - "evidence": [ - "{HTTP/REST endpoints}", - "{gRPC proto files}", - "{Message queue configs}", - "{Dependencies}" - ], - "values": [ - "{Protocol: HTTP/REST, gRPC, Kafka, RabbitMQ, etc.}", - "{Framework: Spring MVC, ASP.NET Core, Express}", - "{Port numbers}", - "{Count: N endpoints, M proto files}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-compliance-requirements/SKILL.md b/.github/skills/fact-compliance-requirements/SKILL.md deleted file mode 100644 index 4de230ea0..000000000 --- a/.github/skills/fact-compliance-requirements/SKILL.md +++ /dev/null @@ -1,131 +0,0 @@ ---- -name: fact-compliance-requirements -description: Identify regulatory compliance needs (GDPR, HIPAA, PCI-DSS, SOX) ---- - -# Compliance Requirements Analysis - -## Purpose -Identify regulatory compliance requirements the application must meet based on data handling, industry, and security measures. - -## Target Files/Locations -- **/README.md, **/COMPLIANCE.md, **/SECURITY.md, **/privacy-policy.md -- **/docs/**/*.md -- **/*.{java,cs,js,py,ts} (data handling, encryption) -- **/application.{properties,yml} (audit logging) - -## Example Patterns -- GDPR: data protection, right to erasure, consent -- HIPAA: PHI, encryption, audit logs -- PCI-DSS: payment data, encryption at rest/transit -- SOX: financial data, audit trails, access controls - -## Analysis Steps - -### 1. Check Documentation -``` -Use Read: **/README.md, **/COMPLIANCE.md, **/SECURITY.md -Use Grep: "GDPR|HIPAA|PCI-DSS|PCI|SOX|Sarbanes-Oxley|ISO 27001|SOC 2" -Files: **/docs/**/*.md -Context: -B 2 -A 3 - -Look for: -- Compliance statements -- Regulatory requirements -- Data handling policies -``` - -### 2. Check for Data Protection Code -``` -Use Grep: "PersonalData|PII|PHI|PaymentData|encrypt|anonymize|pseudonymize" -Files: **/*.{java,cs,js,py} -Context: -B 2 -A 2 - -Indicators: -- Data encryption implementations -- Anonymization functions -- Consent management -- Data retention policies -``` - -### 3. Check for Audit Logging -``` -Use Grep: "AuditLog|audit|trail|compliance.*log" -Files: **/*.{java,cs}, **/application.{properties,yml} - -Look for: -- Audit event logging -- User action tracking -- Data access logs -``` - -### 4. Check for Data Privacy Features -``` -Search for: -- Data export (GDPR right to portability) -- Data deletion (right to erasure) -- Consent management -- Cookie policies - -Use Grep: "exportData|deleteUser|consent|privacy|GDPR" -``` - -### 5. Check Security Measures -``` -Compliance often requires: -- Encryption at rest and in transit -- Access controls (RBAC) -- Multi-factor authentication -- Regular security audits - -Cross-reference with security-implementation analysis -``` - -## Confidence Determination - -### High Confidence -- ✅ Explicit compliance documentation -- ✅ Compliance-specific code features -- ✅ Regulatory requirements mentioned -- **Example**: "GDPR compliant: data encryption, consent management, right-to-erasure API implemented" - -### Medium Confidence -- ⚠️ Some compliance features but not comprehensive -- **Example**: "Encryption and audit logging present, suggests PCI-DSS consideration but not documented" - -### Low Confidence -- ⚠️ No explicit compliance requirements -- ⚠️ Features that could support compliance -- **Example**: "Standard security features, no specific compliance mentioned" - -### Not Applicable -- ❌ Internal tool with no regulatory requirements -- **Example**: "Development utility, no compliance requirements" - -## Output Format - -```json -{ - "input_name": "Compliance Requirements", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Compliance summary}", - "confidence": "high|medium|low", - "evidence": [ - "{Documentation mentions}", - "{Compliance-specific code}", - "{Security features}", - "{Audit logging}" - ], - "values": [ - "{Regulations: GDPR, HIPAA, PCI-DSS, SOX, etc.}", - "{Features: encryption, audit logs, consent}", - "{Data types: PII, PHI, payment data}", - "{Certifications if mentioned}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-container-engine/SKILL.md b/.github/skills/fact-container-engine/SKILL.md deleted file mode 100644 index 4d30e5ecb..000000000 --- a/.github/skills/fact-container-engine/SKILL.md +++ /dev/null @@ -1,108 +0,0 @@ ---- -name: fact-container-engine -description: Identify container runtime being used (Docker, Podman, containerd) ---- - -# Container Engine Analysis - -## Purpose -Determine which container engine the application uses (Docker, Podman, containerd, etc.) through analysis of configuration files, build scripts, and deployment manifests. - -## Target Files/Locations -- **/Dockerfile, **/Containerfile -- **/docker-compose*.yml -- **/.dockerignore -- **/Makefile, **/*.sh (build scripts) -- **/k8s/**/*.yaml, **/*.yaml (Kubernetes manifests) -- **/README.md, **/docs/**/*.md (documentation) -- **/.github/workflows/*.yml, **/.gitlab-ci.yml (CI/CD) - -## Example Patterns to Search -- **Docker**: `docker build`, `docker-compose`, `FROM`, `ENTRYPOINT`, `.dockerignore` -- **Podman**: `podman build`, `podman-compose`, `podman run` -- **containerd**: `ctr`, `nerdctl` -- **Build tools**: `docker buildx`, `buildah`, `kaniko` - -## Analysis Steps - -### 1. Check for Dockerfile/Containerfile -``` -Use Glob: **/Dockerfile, **/Containerfile -- Dockerfile indicates Docker usage -- Containerfile indicates Podman/Buildah compatibility -``` - -### 2. Search for Docker Compose Files -``` -Use Glob: **/docker-compose*.yml -Indicates Docker Compose usage -``` - -### 3. Analyze Build Scripts -``` -Use Grep: "docker|podman|buildah|nerdctl" -Files: **/*.sh, **/Makefile, **/*.bash -Context: -B 2 -A 2 -``` - -### 4. Check CI/CD Pipelines -``` -Use Grep in CI files: -- Pattern: "docker|podman|container" -- Files: **/.github/workflows/*.yml, **/.gitlab-ci.yml -``` - -### 5. Review Documentation -``` -Use Grep in docs: -- Pattern: "docker|podman|container engine" -- Files: **/README.md, **/docs/**/*.md -``` - -## Confidence Determination - -### High Confidence -- ✅ Dockerfile + docker-compose.yml present -- ✅ Build scripts explicitly use docker commands -- ✅ CI/CD uses specific container engine -- **Example**: "Docker engine with Compose v2 based on docker-compose.yml and Dockerfile" - -### Medium Confidence -- ⚠️ Generic Containerfile (Docker/Podman compatible) -- ⚠️ Build scripts don't specify engine -- **Example**: "Containerfile present, compatible with Docker or Podman" - -### Low Confidence -- ⚠️ No explicit engine indicators -- ⚠️ Only Kubernetes manifests (engine unclear) -- **Example**: "Container usage implied but engine unclear" - -### Not Applicable -- ❌ No containerization -- **Example**: "No container files found - traditional deployment" - -## Output Format - -```json -{ - "input_name": "Container Engine", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Container engine identified}", - "confidence": "high|medium|low", - "evidence": [ - "{Files found}", - "{Commands in scripts}", - "{CI/CD configuration}" - ], - "values": [ - "{Engine name: Docker, Podman, etc.}", - "{Compose version if applicable}", - "{Build tools used}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-container-version/SKILL.md b/.github/skills/fact-container-version/SKILL.md deleted file mode 100644 index b18e4faf5..000000000 --- a/.github/skills/fact-container-version/SKILL.md +++ /dev/null @@ -1,99 +0,0 @@ ---- -name: fact-container-version -description: Identify container runtime version (Docker, Podman version) ---- - -# Container Version Analysis - -## Purpose -Determine the version of the container runtime being used through CI/CD configurations, documentation, or system requirements. - -## Target Files/Locations -- **/.github/workflows/*.yml, **/.gitlab-ci.yml -- **/README.md, **/docs/**/*.md -- **/Makefile, **/*.sh -- **/docker-compose.yml (version field) - -## Example Patterns -- `docker-compose version: '3.8'` -- Docker version: 20.10.17 -- Podman version: 4.1.0 - -## Analysis Steps - -### 1. Check docker-compose Version -``` -Use Glob: **/docker-compose*.yml -Use Read to find: version: '3.x' or version: '2.x' -This indicates required Docker Compose version -``` - -### 2. Search CI/CD for Version Specs -``` -Use Grep: "docker.*version|container.*version" -Files: **/.github/workflows/*.yml, **/.gitlab-ci.yml -Context: -B 2 -A 2 -``` - -### 3. Check Documentation -``` -Use Grep: "docker.*[0-9]+\\.[0-9]+|podman.*[0-9]+\\.[0-9]+" -Files: **/README.md, **/docs/**/*.md -Context: -B 1 -A 1 -``` - -### 4. Look for Version Requirements -``` -Search for: -- "requires Docker 20.10+" -- "tested with Podman 4.x" -- Minimum version specifications -``` - -## Confidence Determination - -### High Confidence -- ✅ docker-compose version explicitly set -- ✅ CI/CD specifies container version -- ✅ Documentation states version requirements -- **Example**: "Docker 20.10.17 specified in CI/CD, docker-compose v3.8" - -### Medium Confidence -- ⚠️ docker-compose version found but engine version unclear -- ⚠️ Version mentioned in docs but not enforced -- **Example**: "docker-compose v3.8 indicates Docker 19.03+ requirement" - -### Low Confidence -- ⚠️ No explicit version information -- ⚠️ Version inferred from features used -- **Example**: "Features used suggest Docker 20.10+ but not specified" - -### Not Applicable -- ❌ No containerization -- **Example**: "No container configuration found" - -## Output Format - -```json -{ - "input_name": "Container Version", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Container version information}", - "confidence": "high|medium|low", - "evidence": [ - "{docker-compose version}", - "{CI/CD specifications}", - "{Documentation references}" - ], - "values": [ - "{Engine: Docker/Podman}", - "{Version number or requirement}", - "{Compose version if applicable}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-data-classification/SKILL.md b/.github/skills/fact-data-classification/SKILL.md deleted file mode 100644 index 4cf84c614..000000000 --- a/.github/skills/fact-data-classification/SKILL.md +++ /dev/null @@ -1,151 +0,0 @@ ---- -name: fact-data-classification -description: Identify data sensitivity classification (Public, Internal, Confidential, PII) ---- - -# Data Classification Analysis - -## Purpose -Determine the sensitivity classification of data handled by the application to understand protection requirements. - -## Target Files/Locations -- **/README.md, **/SECURITY.md, **/DATA_CLASSIFICATION.md -- **/docs/**/*.md (data classification) -- **/*.{java,cs,js,py} (data annotations, comments) -- **/schema.sql, **/migrations/*.sql (database schemas) -- **/*.proto, **/*.graphql (API schemas) - -## Example Patterns -- **Public**: publicly accessible data, no protection needed -- **Internal**: company data, standard access controls -- **Confidential**: sensitive business data, strict access controls -- **PII**: personal identifiable information (names, emails, SSN) -- **PHI**: protected health information -- **PCI**: payment card data -- **Restricted**: highly sensitive, executive/legal only - -## Analysis Steps - -### 1. Check Documentation -``` -Use Read: **/README.md, **/SECURITY.md, **/DATA_CLASSIFICATION.md -Use Grep: "data classification|sensitivity|PII|confidential|restricted" -Files: **/docs/**/*.md -Context: -B 2 -A 3 - -Look for: -- Data classification policy -- Sensitivity levels -- Data types handled -``` - -### 2. Analyze Database Schemas -``` -Use Glob: **/schema.sql, **/migrations/*.sql, **/flyway/**/*.sql -Use Read to examine table definitions - -Look for sensitive data indicators: -- email, phone, address (PII) -- ssn, tax_id, national_id (PII) -- credit_card, account_number (PCI) -- password, secret, token (Credentials) -- medical, diagnosis (PHI) -- salary, financial (Confidential) -``` - -### 3. Check Code for Data Annotations -``` -Use Grep: "@PersonalData|@Confidential|@Restricted|@PII|@Sensitive" -Files: **/*.{java,cs} -Context: -B 1 -A 3 - -.NET: [PersonalData], [ProtectedPersonalData] -Java: Custom annotations or comments -``` - -### 4. Analyze API Schemas -``` -Use Glob: **/*.proto, **/*.graphql, **/openapi.yaml -Read schema definitions - -Identify sensitive fields: -- User personal information -- Authentication credentials -- Financial data -- Health information -``` - -### 5. Check Encryption Usage -``` -Encryption usage indicates sensitive data: - -Use Grep: "encrypt|cipher|AES|RSA|hash" -Files: **/*.{java,cs,js,py} -Context: -B 2 -A 2 - -What's encrypted: -- Passwords → Credentials (Confidential) -- Payment info → PCI Data -- Medical records → PHI -- Personal details → PII -``` - -### 6. Categorize Data Types -``` -Based on findings, classify: -- Public: blog posts, product catalogs -- Internal: employee directory, internal docs -- Confidential: trade secrets, financial reports -- PII: customer names, emails, addresses -- PHI: medical records, diagnoses -- PCI: credit card numbers, CVV -``` - -## Confidence Determination - -### High Confidence -- ✅ Data classification documented -- ✅ Sensitive data fields identified in schema -- ✅ Encryption applied to sensitive data -- **Example**: "Data classification: Confidential and PII - handles customer names, emails, addresses (PII) and financial transactions (Confidential)" - -### Medium Confidence -- ⚠️ Data types identified but no formal classification -- **Example**: "Database contains email and address fields (likely PII) but no classification policy documented" - -### Low Confidence -- ⚠️ Data types unclear from schema -- ⚠️ Generic field names -- **Example**: "Database schema generic, data sensitivity unclear" - -### Not Applicable -- ❌ No data storage (stateless API proxy) -- **Example**: "Stateless authentication proxy, no data stored" - -## Output Format - -```json -{ - "input_name": "Data Classification", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Classification summary}", - "confidence": "high|medium|low", - "evidence": [ - "{Documentation}", - "{Database schema analysis}", - "{Sensitive field identification}", - "{Encryption usage}" - ], - "values": [ - "{Classification levels: Public, Internal, Confidential, PII, etc.}", - "{Sensitive data types handled}", - "{Protection measures per classification}", - "{Data volume estimates if available}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-embedded-language-usage/SKILL.md b/.github/skills/fact-embedded-language-usage/SKILL.md deleted file mode 100644 index 90c68df15..000000000 --- a/.github/skills/fact-embedded-language-usage/SKILL.md +++ /dev/null @@ -1,277 +0,0 @@ ---- -name: fact-embedded-language-usage -description: Identifies whether the Java app executes or embeds code in another language or interpreter at runtime ---- - -# Embedded Language Usage Analysis - -## Purpose -Detect if the application executes external processes, embeds scripting engines, or dynamically evaluates code in other languages at runtime. This includes shell command execution, JavaScript engines, Python interpreters, Groovy shells, and embedded SQL/native code execution. - -## Target Files/Locations -- **Java source files**: **/*.java -- **Script files**: **/*.sh, **/*.bat, **/*.ps1 -- **Configuration files**: **/application.{properties,yml}, **/pom.xml, **/build.gradle -- **Resources**: **/resources/**/*.js, **/resources/**/*.py, **/resources/**/*.groovy - -## Example Patterns to Search -- **Process Execution**: `Runtime.getRuntime().exec()`, `ProcessBuilder`, `ProcessBuilder.command()` -- **Script Engines**: `ScriptEngineManager`, `ScriptEngine.eval()`, `Nashorn`, `GraalVM.Polyglot` -- **Embedded Interpreters**: `GroovyShell`, `PythonInterpreter`, `JRuby`, `Jython` -- **Dynamic Evaluation**: `eval()`, `compile()`, `interpret()` -- **Native Code**: `System.loadLibrary()`, `JNI`, `JNA`, `native` keyword -- **SQL Execution**: `Statement.execute()`, `PreparedStatement`, dynamic SQL construction - -## Analysis Steps - -### 1. Search for Process Execution -``` -Use Grep tool to search for external process execution: -- Pattern: "Runtime\\.getRuntime\\(\\)\\.exec|ProcessBuilder|exec\\(|startProcess" -- Files: **/*.java -- Context: -B 2 -A 2 (2 lines before/after for context) - -Look for: -- Shell command execution -- External program invocation -- Dynamic command construction -``` - -### 2. Search for ScriptEngine APIs -``` -Use Grep tool to find scripting engine usage: -- Pattern: "ScriptEngineManager|ScriptEngine|Nashorn|GraalVM|Polyglot" -- Files: **/*.java -- Context: -B 3 -A 3 - -Check for: -- ScriptEngineManager instantiation -- JavaScript, Python, Groovy engine loading -- eval() or compile() calls -``` - -### 3. Search for Embedded Language Interpreters -``` -Use Grep tool to detect interpreter embedding: -- Pattern: "GroovyShell|PythonInterpreter|JRuby|Jython|LuaJ|BeanShell" -- Files: **/*.java -- Context: -B 3 -A 3 - -Identify: -- Groovy script execution -- Python code embedding -- Ruby/Lua interpreters -``` - -### 4. Check for Native Library Loading -``` -Use Grep tool to find native code usage: -- Pattern: "System\\.loadLibrary|System\\.load|native\\s+\\w+|JNI|JNA" -- Files: **/*.java -- Context: -B 2 -A 2 - -Look for: -- JNI native method declarations -- Dynamic library loading -- JNA interface definitions -``` - -### 5. Analyze Dependencies -``` -Use Glob to find build files: -- **/pom.xml -- **/build.gradle -- **/build.gradle.kts - -Use Read to check for dependencies: -- groovy-all, groovy-core -- jython, jruby -- nashorn-core, graalvm-js -- jna, jna-platform -- rhino (Mozilla JavaScript engine) -``` - -### 6. Check for Dynamic SQL Execution -``` -Use Grep tool to find dynamic SQL: -- Pattern: "Statement\\.execute|executeUpdate|executeQuery|createStatement" -- Files: **/*.java -- Context: -B 3 -A 3 - -Analyze for: -- Dynamic SQL string construction -- Concatenated SQL queries -- PreparedStatement vs Statement usage -``` - -### 7. Scan for Script Files in Resources -``` -Use Glob to find embedded scripts: -- **/resources/**/*.js -- **/resources/**/*.py -- **/resources/**/*.groovy -- **/resources/**/*.lua - -If found, these may be loaded and executed at runtime -``` - -## Confidence Determination - -### High Confidence Criteria -Evidence of actual embedded language execution with clear implementation: -- ✅ ScriptEngineManager or ProcessBuilder instantiated with specific parameters -- ✅ Code shows eval() or execute() calls with script content -- ✅ Dependencies include scripting engines (groovy, jython, nashorn) -- ✅ Script files found in resources directory -- ✅ Multiple instances of embedded language usage across codebase - -**Examples**: -- "ScriptEngineManager found in MainController.java:45 with engine.eval(jsCode)" -- "GroovyShell.evaluate() called in RuleEngine.java with 15 .groovy files in resources/" -- "ProcessBuilder executing bash scripts found in 8 locations" - -### Medium Confidence Criteria -Partial evidence or indirect indicators: -- ⚠️ Dependencies include scripting engines but no direct usage found -- ⚠️ Process execution found but limited to standard system commands -- ⚠️ Native library loading present but unclear purpose -- ⚠️ SQL execution detected but mostly using PreparedStatement -- ⚠️ Comments reference scripting but no implementation found - -**Examples**: -- "Groovy dependency in pom.xml but no GroovyShell usage detected" -- "Runtime.exec() called once in utility class for system info" -- "Native library loaded but JNI methods not clearly identified" - -### Low Confidence Criteria -Weak or ambiguous evidence: -- ⚠️ Only standard JDBC operations (no dynamic SQL) -- ⚠️ No scripting dependencies detected -- ⚠️ Process execution in test code only -- ⚠️ Commented-out embedded language code -- ⚠️ Assumptions based on project type - -**Examples**: -- "Standard PreparedStatement usage only, no dynamic SQL" -- "No scripting engine references found" -- "Process execution only in unit tests for test setup" - -### Not Applicable Criteria -When embedded language analysis doesn't apply: -- ❌ Pure Java application with no external language integration -- ❌ Library project with no executable code -- ❌ Static website or documentation-only repository -- ❌ Different platform (e.g., .NET project being analyzed for Java patterns) - -**Examples**: -- "Pure Java Spring Boot REST API with no scripting requirements" -- "Maven plugin project with no runtime execution" -- "Documentation repository with no code" - -## Output Format - -**CRITICAL**: Use the `write_assessment_result` tool (not just output JSON text). - -```json -{ - "input_name": "Embedded Language Usage", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Clear 1-2 sentence summary of embedded language usage}", - "confidence": "high|medium|low", - "evidence": [ - "{Specific file path + line number where usage detected}", - "{Type of embedded language (Groovy, JavaScript, shell, etc.)}", - "{Dependencies or script files found}", - "{Context of usage (business rules, data processing, etc.)}" - ], - "values": [ - "{Scripting engine name and version}", - "{Number of usage instances}", - "{Types of scripts found}", - "{Native libraries loaded}" - ] - }, - "execution_time_seconds": {elapsed_time}, - "timestamp": "{ISO 8601 timestamp}" -} -``` - -**Finding Examples**: -- ✅ Good: "Application uses GroovyShell to execute business rules with 23 .groovy scripts in resources/rules/ directory" -- ✅ Good: "ScriptEngineManager loads Nashorn JavaScript engine to process data transformations in DataProcessor.java" -- ✅ Good: "ProcessBuilder executes shell scripts for deployment automation in 8 service classes" -- ✅ Good: "No embedded language usage detected - pure Java implementation with standard JDBC" -- ❌ Bad: "Scripting is used" -- ❌ Bad: "Found some code execution" - -**Evidence Examples**: -- ✅ Good: "GroovyShell.evaluate() at src/main/java/com/example/rules/RuleEngine.java:67" -- ✅ Good: "groovy-all-2.5.14 dependency in pom.xml with 23 .groovy files in resources/rules/" -- ✅ Good: "ScriptEngineManager initialized in MainController.java:45 with JavaScript engine type" -- ❌ Bad: "Found scripting code" -- ❌ Bad: "Process execution detected" - -## Error Handling - -### 1. No Evidence Found -- Don't fail - report finding as "No embedded language usage detected" -- Set confidence to high if thorough search performed -- Provide reasoning: "Searched for process execution, script engines, and embedded interpreters - none found" - -### 2. Ambiguous Results -- Report what was found with context -- Set confidence to medium -- Explain uncertainty: "Groovy dependency present but no explicit usage found in code" - -### 3. Tool Failures -- If Grep fails, retry with simpler pattern -- If Grep still fails, try Bash with find/grep combination -- If Read fails on large files, try reading with offset/limit -- After 3 retry attempts, return error status with details - -### 4. Large Codebases -- If initial search returns too many results (>500 matches), refine pattern -- Focus on main source directories (exclude test code initially) -- Prioritize by file path (src/main > src/test) -- Sample representative files for detailed analysis - -## Example Complete Analysis - -**Scenario**: Java Spring Boot application with Groovy rule engine - -**Steps Executed**: -1. Grep for ScriptEngine: Found 3 matches in RuleEngine.java -2. Grep for GroovyShell: Found 15 matches across 5 files -3. Glob for .groovy files: Found 23 files in resources/rules/ -4. Read RuleEngine.java: Confirmed GroovyShell usage with dynamic script loading -5. Read pom.xml: Found groovy-all-2.5.14 dependency - -**Result**: -```json -{ - "input_name": "Embedded Language Usage", - "analysis_method": "LLM", - "status": "success", - "result": { - "finding": "Application uses Groovy as embedded scripting engine for business rules execution with 23 rule scripts loaded dynamically at runtime", - "confidence": "high", - "evidence": [ - "GroovyShell.evaluate() at src/main/java/com/example/rules/RuleEngine.java:67-89", - "groovy-all-2.5.14 dependency in pom.xml line 45", - "23 .groovy script files in src/main/resources/rules/ directory", - "RuleLoader.java:34 loads scripts from classpath using ResourceLoader", - "15 GroovyShell usage instances across 5 files in rules package" - ], - "values": [ - "Groovy 2.5.14 (groovy-all)", - "GroovyShell with CompilerConfiguration", - "23 business rule scripts (.groovy)", - "Dynamic rule evaluation at runtime" - ] - }, - "execution_time_seconds": 38.5, - "timestamp": "2026-02-28T10:15:42Z" -} -``` diff --git a/.github/skills/fact-environment-variables/SKILL.md b/.github/skills/fact-environment-variables/SKILL.md deleted file mode 100644 index 2cef10d2a..000000000 --- a/.github/skills/fact-environment-variables/SKILL.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -name: fact-environment-variables -description: Identify container environment variables and configuration ---- - -# Environment Variables Analysis - -## Purpose -Catalog environment variables used in containerized applications for configuration, secrets, and runtime behavior. - -## Target Files/Locations -- **/Dockerfile (ENV instruction) -- **/docker-compose*.yml (environment section) -- **/k8s/**/*.yaml (env, envFrom) -- **/.env, **/.env.example - -## Example Patterns -- `ENV DATABASE_URL=...` -- `environment: - NODE_ENV=production` -- `env: - name: API_KEY` -- `envFrom: - configMapRef` - -## Analysis Steps - -### 1. Check Dockerfile ENV -``` -Use Grep: "^ENV\\s+" -Files: **/Dockerfile -Extract variable names and default values -``` - -### 2. Analyze docker-compose Environment -``` -Use Read: **/docker-compose*.yml -Look for environment: section -Format: KEY=VALUE or array of strings -``` - -### 3. Check Kubernetes ConfigMaps/Secrets -``` -Use Grep: "env:|envFrom:|configMapRef|secretRef" -Files: **/k8s/**/*.yaml -Context: -B 2 -A 5 -``` - -### 4. Check .env Files -``` -Use Glob: **/.env, **/.env.example, **/.env.template -Use Read to list variable names (mask values) -``` - -### 5. Categorize Variables -``` -Group by type: -- Database: DATABASE_URL, DB_HOST, DB_PASSWORD -- API Keys: API_KEY, SECRET_KEY -- Feature Flags: ENABLE_FEATURE_X -- Runtime: NODE_ENV, LOG_LEVEL, DEBUG -``` - -## Confidence Determination - -### High Confidence -- ✅ Variables explicitly defined in multiple files -- ✅ .env.example provides template -- **Example**: "12 environment variables configured including DATABASE_URL, API_KEY, NODE_ENV" - -### Medium Confidence -- ⚠️ Some variables in code but not all documented -- **Example**: "Environment variables used but no .env.example template" - -### Low Confidence -- ⚠️ Variables referenced but not listed -- **Example**: "Application likely uses env vars but none explicitly configured" - -### Not Applicable -- ❌ No container or hardcoded config -- **Example**: "No environment variable usage detected" - -## Output Format - -```json -{ - "input_name": "Environment Variables", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Variables summary}", - "confidence": "high|medium|low", - "evidence": [ - "{Dockerfile ENV count}", - "{docker-compose environment}", - "{K8s ConfigMap/Secret refs}", - "{.env file presence}" - ], - "values": [ - "{Variable names (masked values)}", - "{Categories: database, api, runtime, etc.}", - "{Count: N variables}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-external-dependencies/SKILL.md b/.github/skills/fact-external-dependencies/SKILL.md deleted file mode 100644 index f62559edb..000000000 --- a/.github/skills/fact-external-dependencies/SKILL.md +++ /dev/null @@ -1,124 +0,0 @@ ---- -name: fact-external-dependencies -description: Identify external system dependencies (SQL Server, Redis, LDAP, File shares) ---- - -# External Dependencies Analysis - -## Purpose -Catalog external systems and services the application depends on including databases, caches, authentication services, and storage. - -## Target Files/Locations -- **/application.{properties,yml} (connection strings) -- **/docker-compose*.yml (external services) -- **/*.env.example (dependency URLs) -- **/README.md (setup instructions) -- **/*.{java,cs,js,py} (connection code) - -## Example Patterns -- **Databases**: SQL Server, PostgreSQL, MySQL, Oracle, MongoDB -- **Caches**: Redis, Memcached, Elasticsearch -- **Auth**: LDAP, Active Directory, OAuth providers -- **Storage**: S3, Azure Blob, NFS, SMB -- **Queues**: RabbitMQ, Kafka, SQS, Azure Service Bus - -## Analysis Steps - -### 1. Check Application Configuration -``` -Use Grep: "datasource|database|jdbc:|connectionString|redis|ldap|s3|blob" -Files: **/application.{properties,yml}, **/appsettings*.json -Context: -B 1 -A 2 - -Extract: -- spring.datasource.url=jdbc:sqlserver://... -- ConnectionStrings:DefaultConnection -- redis.host -- ldap.url -``` - -### 2. Check docker-compose Services -``` -Use Read: **/docker-compose*.yml -Identify services: -- postgres, mysql, sqlserver -- redis, memcached -- rabbitmq, kafka -- elasticsearch - -Note: external vs internal dependencies -``` - -### 3. Check Environment Variables -``` -Use Read: **/.env.example -Look for: -- DATABASE_URL -- REDIS_URL -- LDAP_SERVER -- S3_BUCKET -- API endpoint URLs -``` - -### 4. Check Documentation -``` -Use Read: **/README.md -Look for: -- Prerequisites section -- External services setup -- Configuration instructions -``` - -### 5. Search Code for Connections -``` -Use Grep: "SqlConnection|MongoClient|RedisClient|LdapContext|S3Client" -Files: **/*.{java,cs,js,py} -Context: -B 2 -A 3 -``` - -## Confidence Determination - -### High Confidence -- ✅ Dependencies in config + docker-compose + docs -- ✅ Connection strings present -- **Example**: "External dependencies: SQL Server 2019, Redis 6, LDAP (Active Directory), Azure Blob Storage from config and docs" - -### Medium Confidence -- ⚠️ Dependencies referenced but details incomplete -- **Example**: "Database required (connection string present) but type/version unclear" - -### Low Confidence -- ⚠️ Possible dependencies inferred from code -- **Example**: "May use Redis based on client library dependency" - -### Not Applicable -- ❌ Fully self-contained application -- **Example**: "Standalone application with embedded database, no external dependencies" - -## Output Format - -```json -{ - "input_name": "External Dependencies", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Dependencies summary}", - "confidence": "high|medium|low", - "evidence": [ - "{Configuration entries}", - "{docker-compose services}", - "{Documentation}", - "{Connection code}" - ], - "values": [ - "{Dependency: SQL Server, Redis, etc.}", - "{Versions if known}", - "{Purpose: database, cache, auth, storage}", - "{Count: N external systems}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-external-services/SKILL.md b/.github/skills/fact-external-services/SKILL.md deleted file mode 100644 index 96ec40729..000000000 --- a/.github/skills/fact-external-services/SKILL.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -name: fact-external-services -description: Identify external service dependencies (Database, Redis, Message queues) ---- - -# External Services Analysis - -## Purpose -Detect external service dependencies like databases, caches, message queues, and other backend services the application connects to. - -## Target Files/Locations -- **/docker-compose*.yml (services section) -- **/k8s/**/*.yaml (Service, StatefulSet) -- **/application.{properties,yml} -- **/.env, **/.env.example -- **/*.{java,cs,js,py} (connection strings) - -## Example Patterns -- `postgres:13`, `redis:alpine`, `rabbitmq:3-management` -- `spring.datasource.url`, `REDIS_URL`, `MONGODB_URI` - -## Analysis Steps - -### 1. Check docker-compose Services -``` -Use Read: **/docker-compose*.yml -Look for services: beyond the main app (postgres, redis, mongo, rabbitmq, elasticsearch, etc.) -``` - -### 2. Analyze Application Configuration -``` -Use Grep: "datasource|database|redis|mongo|rabbit|kafka|elasticsearch" -Files: **/application.{properties,yml} -Context: -B 1 -A 2 -``` - -### 3. Check Environment Variables -``` -Use Grep: "DATABASE_URL|REDIS_URL|MONGO|RABBITMQ|KAFKA" -Files: **/.env.example, **/Dockerfile, **/k8s/**/*.yaml -``` - -### 4. Search for Connection Strings in Code -``` -Use Grep: "jdbc:|redis://|mongodb://|amqp://" -Files: **/*.{java,cs,js,py} -Context: -B 2 -A 1 -``` - -## Confidence Determination - -### High Confidence -- ✅ Services in docker-compose + connection config -- ✅ Connection strings in application config -- **Example**: "External services: PostgreSQL 13, Redis 6, RabbitMQ 3 from docker-compose and connection strings" - -### Medium Confidence -- ⚠️ References to services but no explicit config -- **Example**: "Database referenced in code but connection details unclear" - -### Low Confidence -- ⚠️ Possible service usage, not confirmed -- **Example**: "May use database based on ORM dependency" - -### Not Applicable -- ❌ Standalone app with no external services -- **Example**: "Static file server, no external dependencies" - -## Output Format - -```json -{ - "input_name": "External Services", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Services summary}", - "confidence": "high|medium|low", - "evidence": [ - "{docker-compose services}", - "{Connection configs}", - "{Environment variables}" - ], - "values": [ - "{Service types: PostgreSQL, Redis, etc.}", - "{Versions}", - "{Count: N services}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-hardware-requirements/SKILL.md b/.github/skills/fact-hardware-requirements/SKILL.md deleted file mode 100644 index b789d5a66..000000000 --- a/.github/skills/fact-hardware-requirements/SKILL.md +++ /dev/null @@ -1,115 +0,0 @@ ---- -name: fact-hardware-requirements -description: Identify minimum hardware requirements (RAM, CPU, disk) ---- - -# Hardware Requirements Analysis - -## Purpose -Extract minimum hardware requirements from documentation, resource configurations, and deployment files. - -## Target Files/Locations -- **/README.md, **/INSTALL.md, **/REQUIREMENTS.md (system requirements) -- **/docs/**/*.md (hardware mentions) -- **/docker-compose*.yml (resource limits) -- **/k8s/**/*.yaml (resource requests/limits) - -## Example Patterns -- "4GB RAM minimum" -- "2 CPU cores recommended" -- "10GB disk space required" -- `limits: memory: "4Gi", cpu: "2"` - -## Analysis Steps - -### 1. Check Documentation -``` -Use Read: **/README.md, **/INSTALL.md, **/REQUIREMENTS.md -Search for: -- "System Requirements" section -- "Hardware Requirements" section -- RAM/memory mentions (GB, GiB) -- CPU mentions (cores, GHz) -- Disk mentions (GB storage) - -Use Grep: "[0-9]+\\s*(GB|GiB|MB|MiB)|[0-9]+\\s*(cores?|CPU)|disk|storage" -Context: -B 2 -A 2 -``` - -### 2. Check Container Resource Limits -``` -Use Read: **/docker-compose*.yml -Look for deploy.resources.limits/reservations - -Use Grep: "memory:|cpu:" -Files: **/k8s/**/*.yaml -Context: -B 3 -A 1 - -Extract resource specifications: -- Memory: 2Gi, 4Gi, 512Mi -- CPU: 1000m, 2, 500m -``` - -### 3. Analyze Resource Patterns -``` -From K8s/Compose: -- limits = maximum resources -- requests/reservations = minimum required - -Calculate totals for multi-container apps -``` - -### 4. Check Database Requirements -``` -If database used, estimate: -- PostgreSQL: ~1GB RAM minimum -- MySQL: ~512MB RAM minimum -- MongoDB: ~1GB RAM minimum -- Plus storage for data -``` - -## Confidence Determination - -### High Confidence -- ✅ Requirements explicitly documented -- ✅ Resource limits configured match docs -- **Example**: "4GB RAM, 2 CPU cores, 10GB disk from README and matching K8s resource requests" - -### Medium Confidence -- ⚠️ Requirements in one source only -- **Example**: "4GB memory limit in docker-compose, no documentation" - -### Low Confidence -- ⚠️ Requirements estimated from resources used -- **Example**: "Estimated 2GB RAM based on container limits, not documented" - -### Not Applicable -- ❌ Serverless or managed platform -- **Example**: "AWS Lambda deployment, hardware not directly specified" - -## Output Format - -```json -{ - "input_name": "Hardware Requirements", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Hardware summary}", - "confidence": "high|medium|low", - "evidence": [ - "{Documentation sections}", - "{Container resource limits}", - "{Calculations}" - ], - "values": [ - "{RAM: minimum and recommended}", - "{CPU: cores or millicores}", - "{Disk: storage requirements}", - "{Network: if specified}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-health-checks/SKILL.md b/.github/skills/fact-health-checks/SKILL.md deleted file mode 100644 index 4c12bd2bf..000000000 --- a/.github/skills/fact-health-checks/SKILL.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -name: fact-health-checks -description: Identify health check configurations (HTTP checks, command checks) ---- - -# Health Checks Analysis - -## Purpose -Identify health check and readiness probe configurations for monitoring application health. - -## Target Files/Locations -- **/Dockerfile (HEALTHCHECK instruction) -- **/docker-compose*.yml (healthcheck section) -- **/k8s/**/*.yaml (livenessProbe, readinessProbe, startupProbe) -- **/*.{java,cs,js,py,go} (health endpoints) - -## Example Patterns -- `HEALTHCHECK CMD curl -f http://localhost/ || exit 1` -- `healthcheck: test: ["CMD", "curl", "-f", "http://localhost"]` -- `livenessProbe: httpGet: path: /health` - -## Analysis Steps - -### 1. Check Dockerfile HEALTHCHECK -``` -Use Grep: "^HEALTHCHECK" -Files: **/Dockerfile -Parse: CMD, interval, timeout, retries -``` - -### 2. Analyze docker-compose Healthchecks -``` -Use Read: **/docker-compose*.yml -Look for healthcheck: section per service - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost/health"] - interval: 30s - timeout: 10s - retries: 3 -``` - -### 3. Check Kubernetes Probes -``` -Use Grep: "livenessProbe:|readinessProbe:|startupProbe:" -Files: **/k8s/**/*.yaml -Context: -A 10 - -Parse probe types: -- httpGet: path + port -- exec: command -- tcpSocket: port -``` - -### 4. Search for Health Endpoints in Code -``` -Use Grep: "/health|/healthz|/ready|/live|HealthCheck" -Files: **/*.{java,cs,js,py,go} -Context: -B 2 -A 5 - -Common frameworks: -- Spring Boot: /actuator/health -- ASP.NET Core: /health -- Express.js: /health -``` - -## Confidence Determination - -### High Confidence -- ✅ Health checks configured in orchestration files -- ✅ Health endpoint exists in code -- **Example**: "Health checks: HTTP GET /health endpoint with 30s interval, 3 retries" - -### Medium Confidence -- ⚠️ Health check configured but endpoint unclear -- **Example**: "HEALTHCHECK defined using curl but endpoint not verified" - -### Low Confidence -- ⚠️ No explicit health checks configured -- **Example**: "No health checks configured, relies on container exit codes" - -### Not Applicable -- ❌ Simple app with no health monitoring needs -- **Example**: "Batch job, health checks not applicable" - -## Output Format - -```json -{ - "input_name": "Health Checks", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Health checks summary}", - "confidence": "high|medium|low", - "evidence": [ - "{Dockerfile HEALTHCHECK}", - "{docker-compose healthcheck}", - "{K8s probes}", - "{Health endpoint in code}" - ], - "values": [ - "{Check type: HTTP, exec, TCP}", - "{Endpoint: /health, /ready, etc.}", - "{Timing: interval, timeout, retries}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-image-layers/SKILL.md b/.github/skills/fact-image-layers/SKILL.md deleted file mode 100644 index ec7e63aa8..000000000 --- a/.github/skills/fact-image-layers/SKILL.md +++ /dev/null @@ -1,102 +0,0 @@ ---- -name: fact-image-layers -description: Analyze container image layer count and structure ---- - -# Image Layers Analysis - -## Purpose -Analyze Dockerfile instructions to understand image layer structure, count of layers, and optimization opportunities. - -## Target Files/Locations -- **/Dockerfile, **/Containerfile - -## Example Patterns -Layer-creating instructions: RUN, COPY, ADD, FROM -Non-layer instructions: ENV, ARG, LABEL, EXPOSE, CMD, ENTRYPOINT, WORKDIR - -## Analysis Steps - -### 1. Count Layer-Creating Instructions -``` -Use Grep to count RUN/COPY/ADD: -- Pattern: "^(RUN|COPY|ADD)\\s+" -- Files: **/Dockerfile, **/Containerfile -- Mode: count - -Each match creates a layer (except multi-stage FROM) -``` - -### 2. Analyze RUN Instruction Complexity -``` -Use Read Dockerfile and analyze: -- Single-command RUN vs multi-command (&&) -- Layer optimization (combined commands) -- Example: RUN apt-get update && apt-get install (1 layer) - vs: RUN apt-get update \n RUN apt-get install (2 layers) -``` - -### 3. Check for Multi-stage Build -``` -Count FROM instructions: -Each stage adds base layers -Final image only includes layers from last stage + COPY --from -``` - -### 4. Identify Layer Size Contributors -``` -Look for large operations: -- Package installations (apt-get, yum, apk) -- File copies (COPY large directories) -- Downloads (wget, curl in RUN) -``` - -## Confidence Determination - -### High Confidence -- ✅ Dockerfile analyzed completely -- ✅ Clear count of layer instructions -- ✅ Multi-stage structure understood -- **Example**: "Image has 8 layers: 3 from base (alpine), 3 RUN, 2 COPY" - -### Medium Confidence -- ⚠️ Some build-time variables affect layers -- ⚠️ External build process unclear -- **Example**: "Approximately 6-8 layers based on Dockerfile instructions" - -### Low Confidence -- ⚠️ Dockerfile uses complex ARGs affecting structure -- ⚠️ BuildKit features that modify layering -- **Example**: "Layer count unclear due to conditional build steps" - -### Not Applicable -- ❌ No Dockerfile -- **Example**: "No container image definition found" - -## Output Format - -```json -{ - "input_name": "Image Layers", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Layer analysis summary}", - "confidence": "high|medium|low", - "evidence": [ - "{RUN instruction count}", - "{COPY/ADD instruction count}", - "{Multi-stage stages}", - "{Optimization patterns observed}" - ], - "values": [ - "{Estimated layer count}", - "{FROM instructions: N}", - "{RUN instructions: N}", - "{COPY/ADD instructions: N}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-image-size/SKILL.md b/.github/skills/fact-image-size/SKILL.md deleted file mode 100644 index 64771d061..000000000 --- a/.github/skills/fact-image-size/SKILL.md +++ /dev/null @@ -1,121 +0,0 @@ ---- -name: fact-image-size -description: Analyze and estimate container image size ---- - -# Image Size Analysis - -## Purpose -Estimate container image size based on base image, installed packages, and copied files. Identify size optimization opportunities. - -## Target Files/Locations -- **/Dockerfile, **/Containerfile -- Application artifacts to be copied - -## Example Patterns -- Base image (alpine: ~5MB, ubuntu: ~70MB, node:16: ~900MB) -- Package installations (apt, yum, apk) -- Application files (JAR, WAR, binaries) - -## Analysis Steps - -### 1. Identify Base Image Size -``` -Read Dockerfile for FROM instruction -Estimate base sizes: -- scratch: 0MB -- alpine:3.15: ~5MB -- distroless: ~20MB -- ubuntu:20.04: ~70MB -- debian:11: ~120MB -- node:16: ~900MB -- openjdk:11: ~600MB -- mcr.microsoft.com/dotnet/runtime:6.0: ~180MB -``` - -### 2. Analyze Package Installations -``` -Use Grep: "apt-get install|apk add|yum install" -Files: **/Dockerfile -Context: -A 3 - -Estimate: -- Few utilities (curl, wget): +5-10MB -- Build tools (gcc, make): +100-200MB -- Large packages (nginx, postgresql): +50-100MB each -``` - -### 3. Check Application Files Size -``` -For COPY/ADD instructions: -- Look for source paths -- Use Bash to check size: du -sh {source_path} -- Common sizes: - - JAR files: 30-150MB - - Node modules: 100-500MB - - .NET publish: 50-100MB -``` - -### 4. Consider Multi-stage Build Efficiency -``` -If multi-stage: -- Build stage size doesn't matter -- Only final stage COPY --from adds to size -- Final image = base + runtime files only -``` - -### 5. Calculate Estimate -``` -Total = Base + Packages + Application Files + Layer Overhead -Layer overhead: ~10-20% for metadata -``` - -## Confidence Determination - -### High Confidence -- ✅ Base image size known -- ✅ Application files sized via filesystem -- ✅ Package installations enumerated -- **Example**: "Estimated 250MB: alpine (5MB) + Java runtime (150MB) + app JAR (85MB) + dependencies (10MB)" - -### Medium Confidence -- ⚠️ Base image known but packages unclear -- ⚠️ Application size estimated without measurement -- **Example**: "Approximately 200-300MB based on Node.js base and typical app size" - -### Low Confidence -- ⚠️ Complex build process with many layers -- ⚠️ Cannot access application files for sizing -- **Example**: "Likely 100MB-1GB range, difficult to estimate without build" - -### Not Applicable -- ❌ No containerization -- **Example**: "No Dockerfile found" - -## Output Format - -```json -{ - "input_name": "Image Size", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Image size estimate with breakdown}", - "confidence": "high|medium|low", - "evidence": [ - "{Base image and size}", - "{Package installations}", - "{Application files size}", - "{Calculation method}" - ], - "values": [ - "{Estimated total size: XMB or X.XGB}", - "{Base image size}", - "{Application layer size}", - "{Dependencies size}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-language-dependencies/SKILL.md b/.github/skills/fact-language-dependencies/SKILL.md deleted file mode 100644 index eefd957c2..000000000 --- a/.github/skills/fact-language-dependencies/SKILL.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -name: fact-language-dependencies -description: Identify language-specific dependencies in container (package.json, requirements.txt, pom.xml) ---- - -# Language Dependencies Analysis - -## Purpose -Identify application-level dependencies through package manifests copied into the container image. - -## Target Files/Locations -- **/package.json, **/package-lock.json (Node.js) -- **/requirements.txt, **/Pipfile (Python) -- **/pom.xml, **/build.gradle (Java) -- **/*.csproj, **/packages.config (.NET) -- **/go.mod, **/go.sum (Go) -- **/Gemfile, **/Gemfile.lock (Ruby) -- **/composer.json (PHP) - -## Example Patterns -- `COPY package*.json ./` -- `RUN pip install -r requirements.txt` -- `RUN npm install --production` -- `RUN mvn clean install` - -## Analysis Steps - -### 1. Identify Copied Dependency Files -``` -Use Grep: "COPY.*(package\\.json|requirements\\.txt|pom\\.xml|.*\\.csproj|go\\.mod|Gemfile|composer\\.json)" -Files: **/Dockerfile -Context: -B 1 -A 1 -``` - -### 2. Read Dependency Files -``` -Use Glob to find dependency files in project: -- **/package.json (read dependencies section) -- **/requirements.txt (read package list) -- **/pom.xml (read <dependencies>) -- **/*.csproj (read <PackageReference>) -``` - -### 3. Count and Categorize Dependencies -``` -For each runtime: -- Node.js: dependencies vs devDependencies -- Python: required packages -- Java: compile, runtime, test scope -- .NET: PackageReference items -- Go: direct vs indirect -``` - -### 4. Check Install Commands -``` -Use Grep: "npm install|pip install|mvn|gradle|dotnet restore|go mod download" -Files: **/Dockerfile -Verify dependencies are installed in image -``` - -## Confidence Determination - -### High Confidence -- ✅ Dependency files copied and installed in Dockerfile -- ✅ Files readable and parseable -- **Example**: "45 Node.js dependencies from package.json installed via npm install" - -### Medium Confidence -- ⚠️ Dependency files exist but install command unclear -- **Example**: "package.json present, installation method not explicit" - -### Low Confidence -- ⚠️ Can't access dependency files -- **Example**: "Dependency files referenced but not readable" - -### Not Applicable -- ❌ No language dependencies (native binary) -- **Example**: "Compiled Go binary with no external dependencies" - -## Output Format - -```json -{ - "input_name": "Language Dependencies", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Dependencies summary}", - "confidence": "high|medium|low", - "evidence": [ - "{Dependency files found}", - "{COPY/install commands}", - "{Dependency count}" - ], - "values": [ - "{Dependency file: package.json, requirements.txt, etc.}", - "{Dependency count}", - "{Key dependencies list}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-licensing-information/SKILL.md b/.github/skills/fact-licensing-information/SKILL.md deleted file mode 100644 index f41f10ea0..000000000 --- a/.github/skills/fact-licensing-information/SKILL.md +++ /dev/null @@ -1,119 +0,0 @@ ---- -name: fact-licensing-information -description: Identify software licensing details from LICENSE files and dependency analysis ---- - -# Licensing Information Analysis - -## Purpose -Extract licensing information for the application and its dependencies to understand compliance requirements. - -## Target Files/Locations -- **/LICENSE, **/LICENSE.txt, **/LICENSE.md -- **/pom.xml (<licenses>) -- **/package.json (license field) -- **/*.csproj (<PackageLicenseExpression>) -- **/NOTICE, **/COPYING, **/COPYRIGHT - -## Example Patterns -- MIT License -- Apache License 2.0 -- GPL v3, LGPL -- BSD 3-Clause -- Commercial/Proprietary - -## Analysis Steps - -### 1. Check License Files -``` -Use Glob: **/LICENSE*, **/COPYING, **/COPYRIGHT -Use Read to examine content - -Identify license type: -- MIT: "Permission is hereby granted, free of charge" -- Apache 2.0: "Apache License, Version 2.0" -- GPL: "GNU General Public License" -- BSD: "Redistribution and use in source and binary forms" -``` - -### 2. Check Build File Licenses -``` -Maven (pom.xml): -Use Grep: "<licenses>|<license>" -Extract: <name>Apache License 2.0</name> - -Node.js (package.json): -Use Read and parse: { "license": "MIT" } - -.NET (*.csproj): -Use Grep: "<PackageLicenseExpression>" -Extract: <PackageLicenseExpression>MIT</PackageLicenseExpression> -``` - -### 3. Check Dependency Licenses -``` -Look for license reports: -- **/license-report.html -- **/licenses/ directory -- THIRD-PARTY-NOTICES - -Tools that generate these: -- maven-license-plugin -- license-checker (npm) -- dotnet list package --include-transitive -``` - -### 4. Scan for Commercial Licenses -``` -Use Grep: "commercial|proprietary|all rights reserved|confidential" -Files: **/LICENSE*, **/README.md -Context: -B 2 -A 2 -``` - -## Confidence Determination - -### High Confidence -- ✅ LICENSE file present with clear text -- ✅ License in build file matches LICENSE file -- **Example**: "MIT License confirmed in LICENSE file and package.json" - -### Medium Confidence -- ⚠️ License file present but type unclear -- ⚠️ Multiple licenses mentioned -- **Example**: "LICENSE file present, appears to be MIT but not standard format" - -### Low Confidence -- ⚠️ No LICENSE file, inferred from comments -- **Example**: "No LICENSE file, copyright header suggests proprietary" - -### Not Applicable -- ❌ Internal tool with no license requirement -- **Example**: "Internal company tool, no formal licensing" - -## Output Format - -```json -{ - "input_name": "Licensing Information", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{License summary}", - "confidence": "high|medium|low", - "evidence": [ - "{LICENSE file presence and content}", - "{Build file license declaration}", - "{Dependency licenses if available}", - "{Copyright notices}" - ], - "values": [ - "{Primary license: MIT, Apache 2.0, etc.}", - "{Dependency licenses if scanned}", - "{Copyright holder}", - "{License compatibility notes}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-multi-stage-build/SKILL.md b/.github/skills/fact-multi-stage-build/SKILL.md deleted file mode 100644 index dde08a83c..000000000 --- a/.github/skills/fact-multi-stage-build/SKILL.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -name: fact-multi-stage-build -description: Check if Dockerfile uses multi-stage build pattern ---- - -# Multi-stage Build Analysis - -## Purpose -Determine if the project uses Docker multi-stage builds for image optimization. - -## Target Files/Locations -- **/Dockerfile, **/Dockerfile.*, **/*.Dockerfile, **/Containerfile - -## Analysis Steps - -### 1. Find Dockerfile(s) -``` -Use Glob: **/Dockerfile, **/Dockerfile.*, **/*.Dockerfile, **/Containerfile -List all matching files -``` - -### 2. Count FROM Instructions -``` -Use Grep: "^FROM\\s+" -Files: (all Dockerfiles found in step 1) - -Count the number of FROM instructions per file: -- 1 FROM = single-stage build -- 2+ FROM = multi-stage build -``` - -### 3. Extract Named Stages -``` -Use Grep: "^FROM\\s+.*\\s+[Aa][Ss]\\s+" -Files: (all Dockerfiles found in step 1) -Context: full line - -Extract stage names from "FROM ... AS stage_name" patterns -``` - -### 4. Verify Stage Usage -``` -Use Grep: "--from=" -Files: (all Dockerfiles found in step 1) - -Check COPY --from=stage_name instructions to verify multi-stage usage -``` - -## Confidence Determination - -### High Confidence -- ✅ Multiple FROM instructions with named stages -- ✅ COPY --from= instructions present -- **Example**: "Multi-stage build detected in Dockerfile with 3 stages (builder, tester, runtime)" - -### Medium Confidence -- ⚠️ Multiple FROM instructions but no named stages -- **Example**: "Multi-stage build with 2 unnamed stages" - -### Low Confidence -- ⚠️ Single FROM instruction -- **Example**: "Single-stage build, no optimization" - -### Not Applicable -- ❌ No Dockerfile found -- **Example**: "No Dockerfile or Containerfile found in project" - -## Output Format - -```json -{ - "input_name": "Multi-stage Build", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Multi-stage build summary}", - "confidence": "high|medium|low", - "evidence": [ - "{Dockerfile locations}", - "{FROM instruction count}", - "{Named stages}" - ], - "values": [ - "{Build type: Multi-stage or Single-stage}", - "{Stage count}", - "{Stage names if available}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-network-settings/SKILL.md b/.github/skills/fact-network-settings/SKILL.md deleted file mode 100644 index 268fb17de..000000000 --- a/.github/skills/fact-network-settings/SKILL.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -name: fact-network-settings -description: Analyze container network configuration (bridge, host, custom networks) ---- - -# Network Settings Analysis - -## Purpose -Identify container networking configuration including network modes, custom networks, and inter-service communication. - -## Target Files/Locations -- **/docker-compose*.yml (networks section) -- **/k8s/**/*.yaml (Service, NetworkPolicy) -- **/Dockerfile (EXPOSE) - -## Example Patterns -- `networks: - frontend - backend` -- `network_mode: bridge|host|none` -- `kind: NetworkPolicy` - -## Analysis Steps - -### 1. Check docker-compose Networks -``` -Use Read: **/docker-compose*.yml -Look for: -- networks: top-level definitions -- service-level network assignments -- network_mode: bridge, host, none, container:name -``` - -### 2. Analyze Kubernetes Networking -``` -Use Grep: "kind: Service|kind: NetworkPolicy|clusterIP|nodePort" -Files: **/k8s/**/*.yaml -Context: -B 1 -A 10 -``` - -### 3. Check for Custom Network Drivers -``` -In docker-compose: -- driver: bridge, overlay, macvlan -- ipam: configuration -- external: true/false -``` - -## Confidence Determination - -### High Confidence -- ✅ Networks explicitly configured -- ✅ Network mode and drivers specified -- **Example**: "Custom bridge network 'app-network' with frontend and backend subnets" - -### Medium Confidence -- ⚠️ Default networking, no custom config -- **Example**: "Uses default bridge network, no custom configuration" - -### Low Confidence -- ⚠️ Networking inferred from service definitions -- **Example**: "Networking likely default, no explicit configuration" - -### Not Applicable -- ❌ Non-networked application -- **Example**: "CLI tool, no network requirements" - -## Output Format - -```json -{ - "input_name": "Network Settings", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Networking summary}", - "confidence": "high|medium|low", - "evidence": [ - "{docker-compose networks}", - "{K8s Services/Policies}", - "{Network mode}" - ], - "values": [ - "{Network names}", - "{Network mode: bridge, host, overlay}", - "{Driver: bridge, overlay, etc.}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-operating-system/SKILL.md b/.github/skills/fact-operating-system/SKILL.md deleted file mode 100644 index 57a37dc44..000000000 --- a/.github/skills/fact-operating-system/SKILL.md +++ /dev/null @@ -1,126 +0,0 @@ ---- -name: fact-operating-system -description: Identify target/current operating system for deployment ---- - -# Operating System Analysis - -## Purpose -Determine the target or current operating system the application runs on, especially for containerized or cloud-native applications. - -## Target Files/Locations -- **/Dockerfile (FROM base image OS) -- **/README.md, **/docs/**/*.md (system requirements) -- **/k8s/**/*.yaml (nodeSelector, node affinity) -- **/pom.xml, **/*.csproj (target runtime identifiers) - -## Example Patterns -- **Container base**: FROM alpine:3.15, FROM ubuntu:20.04, FROM mcr.microsoft.com/windows/servercore:ltsc2019 -- **.NET RID**: linux-x64, win-x64, osx-x64 -- **Node selector**: kubernetes.io/os: linux - -## Analysis Steps - -### 1. Check Container Base Image OS -``` -Use Grep: "^FROM\\s+" -Files: **/Dockerfile -Extract base image name - -Map to OS: -- alpine → Alpine Linux -- ubuntu → Ubuntu Linux -- debian → Debian Linux -- centos/rocky → CentOS/Rocky Linux -- windows/servercore → Windows Server -- mcr.microsoft.com/windows → Windows -``` - -### 2. Check .NET Runtime Identifiers -``` -Use Grep: "RuntimeIdentifier|<RuntimeIdentifiers>" -Files: **/*.csproj - -RIDs: -- linux-x64 → Linux 64-bit -- linux-arm64 → Linux ARM 64-bit -- win-x64 → Windows 64-bit -- win-arm64 → Windows ARM 64-bit -- osx-x64 → macOS Intel -- osx-arm64 → macOS Apple Silicon -``` - -### 3. Check Kubernetes Node Selectors -``` -Use Grep: "nodeSelector:|kubernetes\\.io/os:" -Files: **/k8s/**/*.yaml -Context: -A 3 - -Values: -- linux → Linux nodes -- windows → Windows nodes -``` - -### 4. Check Documentation -``` -Use Read: **/README.md (first 100 lines) -Look for: -- System Requirements section -- OS mentions (Linux, Windows, macOS) -- Installation instructions per OS -``` - -### 5. Check Platform-Specific Scripts -``` -Use Glob: -- **/*.sh → Linux/macOS -- **/*.ps1, **/*.bat → Windows - -Presence indicates OS support -``` - -## Confidence Determination - -### High Confidence -- ✅ OS explicitly defined in container base or RID -- ✅ Documentation confirms OS -- **Example**: "Target OS: Alpine Linux 3.15 from Dockerfile base image" - -### Medium Confidence -- ⚠️ OS inferred from framework or scripts -- **Example**: "Likely Linux (shell scripts present, typical Java deployment)" - -### Low Confidence -- ⚠️ OS unclear, multiple possibilities -- **Example**: "Cross-platform framework, specific OS deployment unclear" - -### Not Applicable -- ❌ Pure Java/JVM with no OS-specific features -- **Example**: "Platform-independent Java library" - -## Output Format - -```json -{ - "input_name": "Operating System", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{OS summary}", - "confidence": "high|medium|low", - "evidence": [ - "{Container base image}", - "{Runtime identifier}", - "{Documentation}", - "{Scripts}" - ], - "values": [ - "{OS: Alpine Linux, Ubuntu, Windows Server, etc.}", - "{Version: 3.15, 20.04, 2019, etc.}", - "{Architecture: x64, arm64}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-orchestration-tool/SKILL.md b/.github/skills/fact-orchestration-tool/SKILL.md deleted file mode 100644 index 00fabf2a4..000000000 --- a/.github/skills/fact-orchestration-tool/SKILL.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -name: fact-orchestration-tool -description: Identify container orchestration platform (Docker Compose, Kubernetes, Docker Swarm) ---- - -# Orchestration Tool Analysis - -## Purpose -Determine which container orchestration tool manages the application deployment. - -## Target Files/Locations -- **/docker-compose*.yml (Docker Compose) -- **/k8s/**/*.yaml, **/*.yaml (Kubernetes) -- **/swarm/ (Docker Swarm) -- **/.github/workflows/, **/.gitlab-ci.yml (CI/CD hints) - -## Example Patterns -- Docker Compose: `docker-compose.yml`, `version: '3.8'` -- Kubernetes: `kind: Deployment`, `apiVersion: apps/v1` -- Docker Swarm: `deploy: mode: replicated` - -## Analysis Steps - -### 1. Check for Docker Compose -``` -Use Glob: **/docker-compose*.yml -If found: Docker Compose is used -Check version: '2.x' or '3.x' -``` - -### 2. Check for Kubernetes Manifests -``` -Use Glob: **/k8s/**/*.yaml, **/manifests/**/*.yaml -Use Grep: "apiVersion:.*apps/v1|kind: Deployment|kind: Service" -Files: **/*.yaml -If Kubernetes resources found: K8s is used -``` - -### 3. Check for Docker Swarm Config -``` -Use Grep: "deploy:|mode: replicated|placement:" -Files: **/docker-compose*.yml -Swarm uses Compose format with deploy: section -``` - -### 4. Check CI/CD for Deployment Hints -``` -Use Grep: "kubectl|helm|docker-compose|docker stack" -Files: **/.github/workflows/*.yml, **/.gitlab-ci.yml -Context: -B 2 -A 2 -``` - -## Confidence Determination - -### High Confidence -- ✅ Clear orchestration files present -- ✅ CI/CD deploys using specific tool -- **Example**: "Kubernetes orchestration with 15 manifests in k8s/ directory and kubectl in CI/CD" - -### Medium Confidence -- ⚠️ Files present but usage unclear -- **Example**: "docker-compose.yml for local dev, K8s for production (multiple tools)" - -### Low Confidence -- ⚠️ No clear orchestration indicators -- **Example**: "Single Dockerfile, orchestration unclear" - -### Not Applicable -- ❌ No container orchestration -- **Example**: "Direct Docker run, no orchestration" - -## Output Format - -```json -{ - "input_name": "Orchestration Tool", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Orchestration summary}", - "confidence": "high|medium|low", - "evidence": [ - "{docker-compose.yml presence}", - "{K8s manifest count and location}", - "{CI/CD deployment commands}" - ], - "values": [ - "{Tool: Docker Compose, Kubernetes, Docker Swarm}", - "{Version: Compose 3.8, K8s 1.25, etc.}", - "{Manifest count}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-profile-settings/SKILL.md b/.github/skills/fact-profile-settings/SKILL.md deleted file mode 100644 index 9112a6b57..000000000 --- a/.github/skills/fact-profile-settings/SKILL.md +++ /dev/null @@ -1,332 +0,0 @@ ---- -name: fact-profile-settings -description: Analyze environment-specific profiles and configuration ---- - -# Profile Settings Analysis - -## Purpose -Identify how the application manages environment-specific configurations (Development, Test, Staging, Production) through profiles, property files, or environment-based settings. This helps understand configuration management complexity and deployment requirements. - -## Target Files/Locations -- **Spring profiles**: **/application-{profile}.{properties,yml}, **/application.{properties,yml} -- **Maven profiles**: **/pom.xml (`<profiles>` section) -- **Gradle profiles**: **/build.gradle, **/build.gradle.kts (buildTypes, productFlavors) -- **Environment configs**: **/config/**, **/environments/**, **/profiles/ -- **Java properties**: **/{env}/*.properties, **/*-{env}.properties -- **.NET configs**: **/appsettings.{Environment}.json, **/web.{config}.config -- **Docker/K8s**: **/docker-compose.{env}.yml, **/k8s/**/*-{env}.yaml -- **CI/CD**: **/.github/workflows/, **/.gitlab-ci.yml, **/Jenkinsfile - -## Example Patterns to Search -- **Spring profiles**: `spring.profiles.active`, `@Profile("dev")`, `application-dev.properties` -- **Maven profiles**: `<profile><id>development</id>`, `<activeByDefault>`, `<activation>` -- **Environment variables**: `${ENV:prod}`, `#{environment}`, `process.env.NODE_ENV` -- **Profile-specific beans**: `@Profile("production")`, `@ConditionalOnProfile` -- **Config file names**: dev, test, staging, uat, prod, production, development - -## Analysis Steps - -### 1. Search for Spring Profile Configuration Files -``` -Use Glob to find Spring profile-specific configs: -- **/application-dev.{properties,yml,yaml} -- **/application-test.{properties,yml,yaml} -- **/application-staging.{properties,yml,yaml} -- **/application-prod.{properties,yml,yaml} -- **/application-production.{properties,yml,yaml} -- **/application-uat.{properties,yml,yaml} -- **/application-local.{properties,yml,yaml} - -Use Read to examine application.properties/yml: -- Check for spring.profiles.active setting -- Look for profile-specific property groups (--- separator in YAML) -- Identify default profile configuration - -Count and categorize profiles found -``` - -### 2. Search for @Profile Annotations in Java Code -``` -Use Grep to find profile usage in code: -Pattern: "@Profile\\(|@ConditionalOnProfile|spring\\.profiles\\." -Files: **/*.java -Context: -B 1 -A 2 - -Analyze: -- Which beans/components are profile-specific -- Profile names used in annotations -- Conditional logic based on profiles -``` - -### 3. Analyze Maven Profile Configuration -``` -Use Glob to find Maven build file: -- **/pom.xml - -Use Read or Grep to search for: -- <profiles> section -- <profile><id> elements (dev, test, prod, etc.) -- <activeByDefault> settings -- <activation> conditions (property, JDK, OS) -- Profile-specific properties, dependencies, plugins - -Example patterns to search: -<profile> - <id>development</id> - <activation> - <activeByDefault>true</activeByDefault> - </activation> -</profile> -``` - -### 4. Check Gradle Build Profiles -``` -Use Glob to find Gradle files: -- **/build.gradle -- **/build.gradle.kts - -Use Read to check for: -- buildTypes { debug, release } -- productFlavors { dev, staging, prod } -- Environment-specific configurations -- ext { profile = project.hasProperty('env') ? env : 'dev' } -``` - -### 5. Search for .NET Environment Configurations -``` -Use Glob to find .NET configs: -- **/appsettings.Development.json -- **/appsettings.Staging.json -- **/appsettings.Production.json -- **/web.Development.config -- **/web.Release.config - -Use Grep in .csproj files: -Pattern: "<Environments>|ASPNETCORE_ENVIRONMENT|IHostEnvironment" -``` - -### 6. Check for Environment-Specific Directories -``` -Use Glob to find environment directories: -- **/config/dev/** -- **/config/prod/** -- **/environments/development/** -- **/profiles/** - -Use Bash to list directory structure: -find . -type d -name "dev" -o -name "test" -o -name "prod" -o -name "staging" | head -20 -``` - -### 7. Analyze Docker/K8s Environment Configurations -``` -Use Glob to find container configs: -- **/docker-compose.dev.yml -- **/docker-compose.prod.yml -- **/k8s/dev/**/*.yaml -- **/k8s/production/**/*.yaml -- **/Dockerfile.dev, **/Dockerfile.prod - -Use Grep to find environment variables: -Pattern: "ENV|ENVIRONMENT|PROFILE" -Files: **/Dockerfile, **/docker-compose*.yml -``` - -### 8. Search for CI/CD Environment Configurations -``` -Use Glob to find CI/CD files: -- **/.github/workflows/**/*.yml -- **/.gitlab-ci.yml -- **/Jenkinsfile -- **/azure-pipelines.yml - -Use Read to check for: -- Environment-based job definitions -- Deployment stages (dev, test, prod) -- Environment-specific variables -``` - -### 9. Count and Categorize Profiles -``` -Aggregate all findings: -- List all profile names found -- Count config files per profile -- Identify primary profiles (dev, test, prod) -- Note any custom or unusual profile names -- Check for profile activation logic -``` - -## Confidence Determination - -### High Confidence Criteria -Clear and comprehensive profile configuration: -- ✅ Multiple profile-specific config files found (dev, test, prod) -- ✅ Profile activation mechanism clearly defined -- ✅ Profile-specific beans or components in code -- ✅ Build tool profiles configured (Maven/Gradle) -- ✅ Consistent naming across different config types -- ✅ Environment-specific properties well documented - -**Examples**: -- "Application uses 4 Spring profiles (dev, test, staging, prod) with separate application-{profile}.yml files and @Profile annotations in 12 configuration classes" -- "Maven build configured with 3 profiles (development, testing, production) using different database connections and feature flags per environment" -- ".NET application with appsettings.{Environment}.json for Development, Staging, and Production with ASPNETCORE_ENVIRONMENT detection" - -### Medium Confidence Criteria -Partial profile configuration or unclear structure: -- ⚠️ Some profile files found but incomplete coverage -- ⚠️ Profiles defined but no clear activation mechanism -- ⚠️ Mixed approaches (some Spring, some environment variables) -- ⚠️ Profile names inconsistent across config types -- ⚠️ Only build profiles without runtime profiles (or vice versa) - -**Examples**: -- "Spring profiles for dev and prod found, but test/staging configs missing" -- "Maven profiles defined but no corresponding Spring profile configs" -- "Environment-based configuration exists but profile names vary (dev vs development, prod vs production)" - -### Low Confidence Criteria -Weak or minimal profile evidence: -- ⚠️ Only default configuration, no environment variants -- ⚠️ Profile files exist but appear unused or outdated -- ⚠️ Single-environment application (dev only) -- ⚠️ Hardcoded values instead of profile-based configs -- ⚠️ Profile references in comments but no implementation - -**Examples**: -- "Only application.properties found, no profile-specific configs" -- "Profile files exist but timestamps suggest not used in 2+ years" -- "Comments mention dev/prod configs but actual implementation uses hardcoded values" - -### Not Applicable Criteria -When profile analysis doesn't apply: -- ❌ Simple utility/library with no environment differences -- ❌ Single-purpose tool with no configuration needs -- ❌ Prototype or demo application -- ❌ Different platform with different config approach -- ❌ Configuration managed entirely external (ConfigMap, external config server) - -**Examples**: -- "Command-line utility with no environment-specific behavior" -- "Demo application with hardcoded sample data" -- "Library project with no deployment configuration" - -## Output Format - -**CRITICAL**: Use the `write_assessment_result` tool (not just output JSON text). - -```json -{ - "input_name": "Profile Settings", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Clear 1-2 sentence summary of profile configuration}", - "confidence": "high|medium|low", - "evidence": [ - "{Number and names of profiles identified}", - "{Profile activation mechanism}", - "{Config files per profile}", - "{Code using profile-specific logic}", - "{Build tool profile configuration}" - ], - "values": [ - "{Profile names: dev, test, staging, prod, etc.}", - "{Config file types: properties, yml, json}", - "{Activation method: spring.profiles.active, Maven, env vars}", - "{Number of profile-specific files}", - "{Profile-annotated components count}" - ] - }, - "execution_time_seconds": {elapsed_time}, - "timestamp": "{ISO 8601 timestamp}" -} -``` - -**Finding Examples**: -- ✅ Good: "Application uses comprehensive Spring profile system with 4 environments (dev, test, staging, prod) managed through application-{profile}.yml files and 15 @Profile-annotated configuration classes" -- ✅ Good: ".NET Core application with environment-based configuration using appsettings.{Environment}.json for Development, Staging, and Production environments activated via ASPNETCORE_ENVIRONMENT" -- ✅ Good: "Maven multi-profile build with 3 profiles (development, testing, production) controlling database connections, logging levels, and feature toggles" -- ✅ Good: "No environment profiles detected - single configuration approach suitable for utility application" -- ❌ Bad: "Profiles exist" -- ❌ Bad: "Environment configuration found" - -**Evidence Examples**: -- ✅ Good: "application-dev.yml, application-test.yml, application-staging.yml, application-prod.yml in src/main/resources/" -- ✅ Good: "15 configuration classes with @Profile annotations: @Profile('dev') in DevDatabaseConfig.java, @Profile('prod') in ProdSecurityConfig.java" -- ✅ Good: "Maven pom.xml defines 3 profiles with <id>development</id>, <id>testing</id>, <id>production</id> at lines 120-185" -- ✅ Good: "spring.profiles.active=dev in application.properties, overridable via SPRING_PROFILES_ACTIVE environment variable" -- ❌ Bad: "Configuration files found" -- ❌ Bad: "Profiles detected in code" - -## Error Handling - -### 1. No Profiles Found -- Verify it's not a single-environment application by design -- Check for alternative configuration mechanisms (external config, env vars only) -- Report finding: "No profile-based configuration detected - single configuration model" -- Set confidence to high if thorough search confirms absence - -### 2. Inconsistent Profile Names -- Report all variations found (dev vs development, prod vs production) -- Note potential misconfiguration risks -- Set confidence to medium -- Recommend standardization in evidence notes - -### 3. Partial Profile Implementation -- List which profiles are complete vs incomplete -- Note missing files (e.g., dev and prod exist but no test) -- Set confidence to medium with caveats - -### 4. Mixed Configuration Approaches -- Document each approach found (Spring profiles, Maven profiles, Docker configs) -- Clarify how they interact or if they're independent -- This is common and valid - report comprehensively - -### 5. Tool Failures -- If Glob returns too many results, refine patterns -- If Read fails on large files, use Grep for specific patterns -- After 3 retries, report partial results with notes - -## Example Complete Analysis - -**Scenario**: Spring Boot microservice with comprehensive profile setup - -**Steps Executed**: -1. Glob for Spring configs: Found application-{dev,test,staging,prod}.yml (4 files) -2. Read application.yml: Found spring.profiles.active: dev -3. Grep for @Profile: Found 18 matches in 15 configuration classes -4. Read pom.xml: No Maven profiles (uses Spring profiles only) -5. Checked Docker: Found docker-compose.dev.yml and docker-compose.prod.yml -6. Glob for K8s: Found k8s/dev/ and k8s/prod/ directories with manifests - -**Result**: -```json -{ - "input_name": "Profile Settings", - "analysis_method": "LLM", - "status": "success", - "result": { - "finding": "Application implements comprehensive 4-environment profile system (dev, test, staging, prod) using Spring profiles with separate YAML configs, 15 profile-annotated configuration classes, and environment-specific Docker/Kubernetes manifests", - "confidence": "high", - "evidence": [ - "4 Spring profile configs: application-dev.yml, application-test.yml, application-staging.yml, application-prod.yml in src/main/resources/", - "application.yml sets spring.profiles.active: dev as default, overridable via SPRING_PROFILES_ACTIVE env var", - "15 configuration classes use @Profile annotations: DevDatabaseConfig, ProdSecurityConfig, TestEmailConfig, etc.", - "docker-compose.dev.yml and docker-compose.prod.yml set different SPRING_PROFILES_ACTIVE values", - "Kubernetes manifests in k8s/dev/ and k8s/prod/ directories with environment-specific ConfigMaps", - "Profile-specific settings: database URLs, Redis hosts, feature flags, logging levels" - ], - "values": [ - "4 environments: dev, test, staging, prod", - "Spring profile activation via application.yml and environment variables", - "15 @Profile-annotated configuration classes", - "4 profile-specific YAML files (150-200 lines each)", - "Docker Compose configs for dev and prod", - "Kubernetes ConfigMaps per environment" - ] - }, - "execution_time_seconds": 35.2, - "timestamp": "2026-02-28T10:26:14Z" -} -``` diff --git a/.github/skills/fact-resource-limits/SKILL.md b/.github/skills/fact-resource-limits/SKILL.md deleted file mode 100644 index ec0d4b3b8..000000000 --- a/.github/skills/fact-resource-limits/SKILL.md +++ /dev/null @@ -1,103 +0,0 @@ ---- -name: fact-resource-limits -description: Identify CPU/Memory resource limits for containers ---- - -# Resource Limits Analysis - -## Purpose -Identify CPU and memory resource constraints configured for containers through compose files or Kubernetes manifests. - -## Target Files/Locations -- **/docker-compose*.yml (deploy.resources) -- **/k8s/**/*.yaml (resources.requests, resources.limits) -- **/Dockerfile (no resource limits, but checked for context) - -## Example Patterns -- `memory: 2g`, `cpus: '1.5'` -- `limits: memory: "2Gi", cpu: "1000m"` -- `requests: memory: "512Mi", cpu: "250m"` - -## Analysis Steps - -### 1. Check docker-compose Resources -``` -Use Read: **/docker-compose*.yml -Look for deploy.resources section: - deploy: - resources: - limits: - cpus: '2.0' - memory: 2G - reservations: - cpus: '0.5' - memory: 512M -``` - -### 2. Analyze Kubernetes Resources -``` -Use Grep: "resources:|limits:|requests:|memory:|cpu:" -Files: **/k8s/**/*.yaml -Context: -B 3 -A 3 - -Parse format: - resources: - limits: - memory: "2Gi" - cpu: "1000m" - requests: - memory: "512Mi" - cpu: "250m" -``` - -### 3. Check for Resource Quotas -``` -Use Grep: "kind: ResourceQuota" -Files: **/k8s/**/*.yaml -Namespace-level resource constraints -``` - -## Confidence Determination - -### High Confidence -- ✅ Resource limits explicitly configured -- ✅ Both limits and requests defined -- **Example**: "Container limits: 2Gi memory, 1000m CPU; requests: 512Mi memory, 250m CPU" - -### Medium Confidence -- ⚠️ Only limits or only requests defined -- **Example**: "Memory limit 2GB configured, CPU unspecified" - -### Low Confidence -- ⚠️ No explicit resource configuration -- **Example**: "No resource limits configured, uses node defaults" - -### Not Applicable -- ❌ No container orchestration -- **Example**: "Direct Docker run, no resource management" - -## Output Format - -```json -{ - "input_name": "Resource Limits", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Resources summary}", - "confidence": "high|medium|low", - "evidence": [ - "{docker-compose deploy.resources}", - "{K8s limits/requests}", - "{ResourceQuota if present}" - ], - "values": [ - "{Memory limits and requests}", - "{CPU limits and requests}", - "{Units: Gi, Mi, millicores}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-runtime-environment/SKILL.md b/.github/skills/fact-runtime-environment/SKILL.md deleted file mode 100644 index 70d33be50..000000000 --- a/.github/skills/fact-runtime-environment/SKILL.md +++ /dev/null @@ -1,107 +0,0 @@ ---- -name: fact-runtime-environment -description: Identify application runtime (Node.js, Python, Java, Go, .NET) in container ---- - -# Runtime Environment Analysis - -## Purpose -Detect the application runtime/platform used in containerized applications (Node.js, Python, Java, Go, .NET, Ruby, PHP, etc.). - -## Target Files/Locations -- **/Dockerfile, **/Containerfile (FROM base image) -- **/package.json, **/package-lock.json (Node.js) -- **/pom.xml, **/build.gradle (Java) -- **/*.csproj, **/*.sln (.NET) -- **/requirements.txt, **/Pipfile, **/pyproject.toml (Python) -- **/go.mod (Go) -- **/Gemfile (Ruby) -- **/composer.json (PHP) - -## Example Patterns -- `FROM node:16`, `FROM python:3.9`, `FROM openjdk:11`, `FROM mcr.microsoft.com/dotnet/runtime:6.0` -- `RUN npm install`, `RUN pip install`, `RUN go build`, `RUN dotnet publish` - -## Analysis Steps - -### 1. Analyze Base Image -``` -Use Grep: "FROM\\s+(node|python|openjdk|golang|mcr.microsoft.com/dotnet|ruby|php)" -Files: **/Dockerfile -Extract runtime from base image name -``` - -### 2. Check Build/Install Commands -``` -Use Grep: "npm|pip|maven|gradle|dotnet|go build|bundle|composer" -Files: **/Dockerfile -Context: -B 1 -A 2 -``` - -### 3. Identify Dependency Files -``` -Use Glob to find: -- package.json, package-lock.json (Node.js) -- requirements.txt, Pipfile (Python) -- pom.xml, build.gradle (Java) -- *.csproj, *.sln (.NET) -- go.mod, go.sum (Go) -- Gemfile (Ruby) -- composer.json (PHP) -``` - -### 4. Check Version from Base Image -``` -Parse FROM instruction: -- node:16-alpine → Node.js 16 -- python:3.9-slim → Python 3.9 -- openjdk:11-jre → Java 11 -- mcr.microsoft.com/dotnet/runtime:6.0 → .NET 6 -``` - -## Confidence Determination - -### High Confidence -- ✅ Runtime base image clearly specified -- ✅ Build commands match runtime -- ✅ Dependency files present -- **Example**: "Node.js 16 runtime based on FROM node:16-alpine and package.json" - -### Medium Confidence -- ⚠️ Generic base with runtime installed -- ⚠️ Multi-language project -- **Example**: "Java application, version unclear (uses maven but base is Ubuntu)" - -### Low Confidence -- ⚠️ No clear runtime indicators -- **Example**: "Compiled binary, runtime unclear" - -### Not Applicable -- ❌ No container -- **Example**: "No Dockerfile found" - -## Output Format - -```json -{ - "input_name": "Runtime Environment", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Runtime identified}", - "confidence": "high|medium|low", - "evidence": [ - "{Base image}", - "{Build commands}", - "{Dependency files}" - ], - "values": [ - "{Runtime: Node.js, Python, Java, .NET, Go, etc.}", - "{Version}", - "{Variant: alpine, slim, etc.}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-security-implementation/SKILL.md b/.github/skills/fact-security-implementation/SKILL.md deleted file mode 100644 index 7f118bd52..000000000 --- a/.github/skills/fact-security-implementation/SKILL.md +++ /dev/null @@ -1,142 +0,0 @@ ---- -name: fact-security-implementation -description: Analyze security measures (HTTPS, Encryption, Token-based auth) ---- - -# Security Implementation Analysis - -## Purpose -Identify security measures implemented in the application including HTTPS, authentication, authorization, encryption, and security headers. - -## Target Files/Locations -- **/application.{properties,yml} (SSL, security configs) -- **/*.java, **/*.cs (security filters, auth) -- **/pom.xml, **/build.gradle, **/*.csproj (security dependencies) -- **/Dockerfile (SSL certificates) -- **/k8s/**/*.yaml (TLS secrets) - -## Example Patterns -- **HTTPS/TLS**: server.ssl.*, SSLContext, TLS certificates -- **Authentication**: JWT, OAuth2, Basic Auth, API keys -- **Authorization**: @PreAuthorize, [Authorize], RBAC -- **Encryption**: AES, RSA, EncryptionService -- **Security Headers**: CORS, CSP, HSTS, X-Frame-Options - -## Analysis Steps - -### 1. Check for HTTPS/TLS Configuration -``` -Use Grep: "server\\.ssl|https://|TLS|SSLContext|keystore" -Files: **/application.{properties,yml}, **/*.java, **/*.cs -Context: -B 2 -A 2 - -Check for: -- server.ssl.key-store -- SSLContext.getInstance("TLS") -- HTTPS redirect configurations -``` - -### 2. Check Authentication Mechanisms -``` -Use Grep: "JWT|OAuth2|@EnableWebSecurity|JwtToken|Bearer|Basic Auth" -Files: **/*.{java,cs,js} -Context: -B 3 -A 3 - -Dependencies: -- spring-boot-starter-security -- Microsoft.AspNetCore.Authentication.JwtBearer -- passport (Node.js) - -Look for: -- @EnableWebSecurity, @PreAuthorize (Spring) -- [Authorize], UseAuthentication() (.NET) -- JWT token generation/validation -``` - -### 3. Check for Authorization -``` -Use Grep: "@PreAuthorize|@Secured|\\[Authorize\\]|hasRole|hasAuthority" -Files: **/*.{java,cs} - -RBAC indicators: -- Role definitions -- Permission checks -- Access control lists -``` - -### 4. Check for Encryption -``` -Use Grep: "AES|RSA|encrypt|decrypt|Cipher|CryptoService" -Files: **/*.{java,cs,py,js} - -Look for: -- Data encryption at rest -- Encryption services -- Key management (KMS, KeyVault) -``` - -### 5. Check for Security Headers -``` -Use Grep: "CORS|Content-Security-Policy|X-Frame-Options|HSTS|Strict-Transport-Security" -Files: **/*.{java,cs,js}, **/application.{properties,yml} - -Spring: WebMvcConfigurer.addCorsMappings -.NET: app.UseCors(), app.UseHsts() -``` - -### 6. Check Dependency Scanning -``` -Look for security scanning: -- Dependabot config -- Snyk, OWASP Dependency Check -- npm audit, dotnet list package --vulnerable -``` - -## Confidence Determination - -### High Confidence -- ✅ Multiple security measures implemented -- ✅ HTTPS + authentication + authorization configured -- **Example**: "HTTPS with TLS 1.3, JWT authentication, role-based authorization, AES encryption for sensitive data" - -### Medium Confidence -- ⚠️ Some security features but incomplete -- **Example**: "Basic authentication configured, HTTPS unclear" - -### Low Confidence -- ⚠️ Security dependencies present but implementation unclear -- **Example**: "Security framework dependency but no explicit configuration found" - -### Not Applicable -- ❌ Internal tool with no security requirements -- **Example**: "Development utility, no security implementation needed" - -## Output Format - -```json -{ - "input_name": "Security Implementation", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Security summary}", - "confidence": "high|medium|low", - "evidence": [ - "{HTTPS/TLS configuration}", - "{Authentication mechanism}", - "{Authorization approach}", - "{Encryption usage}", - "{Security headers}" - ], - "values": [ - "{Transport: HTTPS, TLS 1.2/1.3}", - "{Auth: JWT, OAuth2, Basic}", - "{Authorization: RBAC, attribute-based}", - "{Encryption: AES-256, RSA}", - "{Headers: CORS, CSP, HSTS}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-service-definition/SKILL.md b/.github/skills/fact-service-definition/SKILL.md deleted file mode 100644 index ac3287c5c..000000000 --- a/.github/skills/fact-service-definition/SKILL.md +++ /dev/null @@ -1,108 +0,0 @@ ---- -name: fact-service-definition -description: Analyze service definition files (docker-compose.yml, K8s manifests) ---- - -# Service Definition Analysis - -## Purpose -Catalog and analyze service definition files that describe how the application is deployed and orchestrated. - -## Target Files/Locations -- **/docker-compose*.yml, **/docker-compose.*.yml -- **/k8s/**/*.yaml, **/manifests/**/*.yaml -- **/helm/**/templates/*.yaml - -## Example Patterns -- Docker Compose services with image, ports, volumes, environment -- Kubernetes Deployments, Services, ConfigMaps, Secrets -- Helm charts with templated manifests - -## Analysis Steps - -### 1. Catalog Docker Compose Files -``` -Use Glob: **/docker-compose*.yml -For each file: -- Count services defined -- Check for extends or depends_on -- Note environment variants (dev, prod) -``` - -### 2. Catalog Kubernetes Manifests -``` -Use Glob: **/k8s/**/*.yaml, **/manifests/**/*.yaml -Use Grep: "kind: Deployment|kind: Service|kind: ConfigMap|kind: Secret" -Files: **/*.yaml -Count by kind - -Group by resource type: -- Workloads: Deployment, StatefulSet, DaemonSet, Job -- Services: Service, Ingress -- Config: ConfigMap, Secret -- Storage: PersistentVolumeClaim -``` - -### 3. Check for Helm Charts -``` -Use Glob: **/Chart.yaml, **/values.yaml -If found: Helm is used -Count template files -``` - -### 4. Analyze Service Complexity -``` -For Compose: -- Single service vs multi-service -- Service dependencies (depends_on) - -For K8s: -- Microservices count -- Service mesh indicators -- Namespace organization -``` - -## Confidence Determination - -### High Confidence -- ✅ Service files present and parseable -- ✅ Clear service structure -- **Example**: "3-service docker-compose with web, api, database services fully configured" - -### Medium Confidence -- ⚠️ Some service files found but structure unclear -- **Example**: "K8s manifests present but relationships between services unclear" - -### Low Confidence -- ⚠️ Service definitions incomplete -- **Example**: "Partial service definitions, missing critical resources" - -### Not Applicable -- ❌ No service definitions -- **Example**: "Single container, no orchestration files" - -## Output Format - -```json -{ - "input_name": "Service Definition", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Service definitions summary}", - "confidence": "high|medium|low", - "evidence": [ - "{File types and counts}", - "{Service/resource counts}", - "{Orchestration approach}" - ], - "values": [ - "{Tool: Compose, K8s, Helm}", - "{Service count}", - "{Resource types and counts}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-servlet-container/SKILL.md b/.github/skills/fact-servlet-container/SKILL.md deleted file mode 100644 index cf206f209..000000000 --- a/.github/skills/fact-servlet-container/SKILL.md +++ /dev/null @@ -1,310 +0,0 @@ ---- -name: fact-servlet-container -description: Identify servlet container requirements and version ---- - -# Servlet Container Analysis - -## Purpose -Identify the servlet container (application server) requirements for the application, including Servlet API version, container-specific features, and deployment model. This helps determine migration compatibility and modernization path. - -## Target Files/Locations -- **Deployment descriptors**: **/WEB-INF/web.xml, **/META-INF/weblogic.xml, **/META-INF/jboss-web.xml -- **Build files**: **/pom.xml, **/build.gradle, **/build.gradle.kts -- **Configuration**: **/application.properties, **/application.yml -- **Server configs**: **/server.xml, **/context.xml (Tomcat), **/standalone.xml (JBoss/WildFly) -- **Java source**: **/*Servlet.java, **/*Filter.java, **/*Listener.java - -## Example Patterns to Search -- **Servlet API**: `javax.servlet`, `jakarta.servlet`, `ServletContext`, `HttpServlet` -- **Servlet version**: `<web-app version="3.1"`, `version="4.0"`, `version="5.0"` -- **Container-specific**: `weblogic`, `jboss`, `wildfly`, `tomcat`, `websphere`, `glassfish` -- **Server dependencies**: `provided` scope for servlet-api, tomcat-embed, wildfly-swarm -- **Annotations**: `@WebServlet`, `@WebFilter`, `@WebListener`, `@MultipartConfig` - -## Analysis Steps - -### 1. Analyze web.xml Deployment Descriptor -``` -Use Glob to find web.xml: -- **/WEB-INF/web.xml -- **/META-INF/web.xml - -If found, use Read to examine: -- <web-app> version attribute (2.3, 2.4, 2.5, 3.0, 3.1, 4.0, 5.0, 6.0) -- XSD/DTD namespace for Servlet version - - Java EE 5 (Servlet 2.5): java.sun.com/xml/ns/javaee - - Java EE 6-7 (Servlet 3.0-3.1): xmlns.jcp.org/xml/ns/javaee - - Jakarta EE 8+ (Servlet 4.0+): jakarta.ee/xml/ns/jakartaee -- Servlet definitions and mappings -- Filter configurations -- Container-specific elements -``` - -### 2. Check Build Dependencies for Servlet API -``` -Use Glob to find build files: -- **/pom.xml -- **/build.gradle -- **/build.gradle.kts - -Use Read or Grep to search for: -Maven (pom.xml): -- <artifactId>servlet-api</artifactId> -- <artifactId>javax.servlet-api</artifactId> -- <artifactId>jakarta.servlet-api</artifactId> -- <version>3.1.0</version>, <version>4.0.0</version>, <version>5.0.0</version> -- <scope>provided</scope> (indicates external container) - -Gradle (build.gradle): -- providedCompile 'javax.servlet:javax.servlet-api:3.1.0' -- compileOnly 'jakarta.servlet:jakarta.servlet-api:5.0.0' - -Check for embedded container dependencies: -- spring-boot-starter-web (embedded Tomcat) -- tomcat-embed-core -- jetty-server -- undertow-core -``` - -### 3. Search for Container-Specific Configuration -``` -Use Glob to find container configs: -- **/META-INF/weblogic.xml (Oracle WebLogic) -- **/META-INF/jboss-web.xml (JBoss/WildFly) -- **/META-INF/glassfish-web.xml (GlassFish) -- **/META-INF/geronimo-web.xml (Apache Geronimo) -- **/WEB-INF/ibm-web-ext.xml (IBM WebSphere) - -If found, this indicates container-specific features/requirements -``` - -### 4. Search for Servlet/Filter Implementations -``` -Use Grep to find servlet code: -Pattern: "extends\\s+HttpServlet|implements\\s+Servlet|implements\\s+Filter" -Files: **/*.java -Context: -B 2 -A 5 - -Use Grep to find servlet annotations: -Pattern: "@WebServlet|@WebFilter|@WebListener|@MultipartConfig" -Files: **/*.java -Context: -B 1 -A 3 - -Analyze: -- Count of servlets/filters -- Use of Servlet 3.0+ annotations vs web.xml -- Async servlet support (@WebServlet(asyncSupported=true)) -``` - -### 5. Check for Spring Boot Embedded Container -``` -Use Grep in pom.xml or build.gradle: -Pattern: "spring-boot-starter-web|spring-boot-starter-tomcat|spring-boot-starter-jetty|spring-boot-starter-undertow" - -If found: -- This is embedded container (not external) -- Check application.properties for server configuration - - server.port - - server.servlet.context-path - - server.tomcat.* (if Tomcat) - -Use Glob for Spring Boot config: -- **/application.properties -- **/application.yml -``` - -### 6. Identify Container Version from Dependencies -``` -Parse Maven/Gradle files for exact versions: -- javax.servlet-api: 2.5, 3.0, 3.1 (Java EE) -- jakarta.servlet-api: 4.0 (Jakarta EE 8), 5.0 (Jakarta EE 9), 6.0 (Jakarta EE 10) - -Match to Servlet API specifications: -- Servlet 2.5 = Java EE 5 (Tomcat 6, JBoss 5) -- Servlet 3.0 = Java EE 6 (Tomcat 7, JBoss 7, GlassFish 3) -- Servlet 3.1 = Java EE 7 (Tomcat 8, WildFly 8-10, WebLogic 12c) -- Servlet 4.0 = Java EE 8 / Jakarta EE 8 (Tomcat 9, WildFly 14+) -- Servlet 5.0 = Jakarta EE 9 (Tomcat 10, WildFly 22+) -- Servlet 6.0 = Jakarta EE 10 (Tomcat 10.1+, WildFly 27+) -``` - -### 7. Check for Container-Specific Features -``` -Use Grep to search for container-specific APIs: -- WebLogic: "weblogic\\..*|WorkManager|JMS" -- JBoss/WildFly: "org\\.jboss|org\\.wildfly|EJB" -- WebSphere: "com\\.ibm\\.websphere" -- Tomcat: "org\\.apache\\.catalina|org\\.apache\\.tomcat" - -These indicate tight coupling to specific containers -``` - -## Confidence Determination - -### High Confidence Criteria -Clear and definitive evidence of servlet container requirements: -- ✅ web.xml present with explicit version attribute -- ✅ Servlet API dependency with specific version in build file -- ✅ Container-specific configuration files found -- ✅ Servlet/Filter implementations found in code -- ✅ Clear deployment model (embedded vs external container) - -**Examples**: -- "Web application requires Servlet 3.1 API (Java EE 7) based on web.xml version='3.1' and javax.servlet-api:3.1.0 dependency" -- "Spring Boot application with embedded Tomcat 9.0.65 (Servlet 4.0) from spring-boot-starter-web:2.7.3" -- "WebLogic-specific deployment with weblogic.xml and WorkManager configuration - requires Oracle WebLogic 12c+" - -### Medium Confidence Criteria -Partial evidence or inferred information: -- ⚠️ Servlet API dependency present but no web.xml (annotation-based config) -- ⚠️ Container type inferred from Spring Boot starter but version unclear -- ⚠️ Servlet code found but no explicit version indicators -- ⚠️ Legacy web.xml without version attribute -- ⚠️ Mixed signals (multiple container dependencies) - -**Examples**: -- "Servlet 3.0+ usage inferred from @WebServlet annotations, but no explicit version in dependencies" -- "Spring Boot with default embedded container (likely Tomcat) but version not specified" -- "Servlet implementations found but build file doesn't declare servlet-api dependency explicitly" - -### Low Confidence Criteria -Weak or ambiguous evidence: -- ⚠️ No web.xml or servlet annotations found -- ⚠️ Servlet API in transitive dependencies only -- ⚠️ Container type unclear or multiple possibilities -- ⚠️ Test code has servlet dependencies but main code doesn't -- ⚠️ Comments reference servlets but no actual implementation - -**Examples**: -- "Servlet API appears in dependency tree but no servlet code found" -- "No clear servlet container indicators - may be non-web application" -- "Test dependencies include servlet-api but unclear if production code uses it" - -### Not Applicable Criteria -When servlet container analysis doesn't apply: -- ❌ Non-web application (standalone, batch, CLI tool) -- ❌ REST API using JAX-RS without servlets (Jersey, RESTEasy standalone) -- ❌ Pure reactive application (Spring WebFlux on Netty) -- ❌ Different platform (.NET, Node.js, Python) -- ❌ Library/framework project (no executable component) - -**Examples**: -- "Spring Boot application using WebFlux and Netty - no servlet container required" -- "Standalone Java application with no web components" -- "Node.js Express application - servlet analysis not applicable" - -## Output Format - -**CRITICAL**: Use the `write_assessment_result` tool (not just output JSON text). - -```json -{ - "input_name": "Servlet Container", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Clear 1-2 sentence summary of servlet container requirements}", - "confidence": "high|medium|low", - "evidence": [ - "{web.xml version or absence}", - "{Servlet API dependency with version}", - "{Container-specific files or features}", - "{Deployment model (embedded/external)}", - "{Container type identified}" - ], - "values": [ - "{Servlet API version (e.g., 3.1, 4.0, 5.0)}", - "{Java EE / Jakarta EE version}", - "{Container type and version (if known)}", - "{Number of servlets/filters}", - "{Container-specific features used}" - ] - }, - "execution_time_seconds": {elapsed_time}, - "timestamp": "{ISO 8601 timestamp}" -} -``` - -**Finding Examples**: -- ✅ Good: "Application requires Servlet 3.1 container (Java EE 7) deployed to external WebLogic 12c server with WebLogic-specific WorkManager configuration" -- ✅ Good: "Spring Boot application with embedded Tomcat 9.0.65 (Servlet 4.0 / Jakarta EE 8) managed by spring-boot-starter-web" -- ✅ Good: "Modern Jakarta EE 9 application requiring Servlet 5.0 container (WildFly 22+, Tomcat 10+) with 8 servlets and 5 filters" -- ✅ Good: "Non-web application - no servlet container required (standalone Spring Boot with WebFlux)" -- ❌ Bad: "Uses servlets" -- ❌ Bad: "Container required" - -**Evidence Examples**: -- ✅ Good: "web.xml at WEB-INF/web.xml declares version='3.1' with Java EE 7 namespace" -- ✅ Good: "javax.servlet-api:3.1.0 with <scope>provided</scope> in pom.xml" -- ✅ Good: "weblogic.xml found with WorkManager 'default' configuration" -- ✅ Good: "spring-boot-starter-web:2.7.3 includes tomcat-embed-core:9.0.65" -- ✅ Good: "5 servlets using @WebServlet annotations (Servlet 3.0+ feature)" -- ❌ Bad: "web.xml exists" -- ❌ Bad: "Container dependency found" - -## Error Handling - -### 1. No Servlet Evidence Found -- Check if this is a web application at all -- Search for alternative web frameworks (JAX-RS, Spring WebFlux) -- Report as "not_applicable" if truly not a web app -- If web app with no servlet evidence: low confidence "Unable to determine container requirements" - -### 2. Mixed Servlet API Versions -- Report all versions found with locations -- Set confidence to medium -- Note potential migration status: "Project in transition from javax.servlet to jakarta.servlet" - -### 3. Embedded vs External Container Confusion -- Check for spring-boot packaging (jar vs war) -- Spring Boot jar = embedded, war = external -- Report both possibilities if unclear - -### 4. Tool Failures -- If Grep fails on large pom.xml, use Read with offset/limit -- If XML parsing issues, try grep for version patterns -- After 3 retries, report with caveats - -## Example Complete Analysis - -**Scenario**: Traditional Java EE web application on WebLogic - -**Steps Executed**: -1. Glob for web.xml: Found WEB-INF/web.xml -2. Read web.xml: version="3.1", Java EE 7 namespace -3. Read pom.xml: javax.servlet-api:3.1.0 with provided scope -4. Glob for container configs: Found META-INF/weblogic.xml -5. Grep for servlets: Found 8 servlet classes, 5 filters -6. Grep for container features: Found WorkManager references - -**Result**: -```json -{ - "input_name": "Servlet Container", - "analysis_method": "LLM", - "status": "success", - "result": { - "finding": "Application requires Servlet 3.1 (Java EE 7) container, specifically Oracle WebLogic 12c, with WebLogic-specific features including WorkManager and JMS integration", - "confidence": "high", - "evidence": [ - "web.xml at WEB-INF/web.xml with version='3.1' and Java EE 7 namespace (xmlns.jcp.org/xml/ns/javaee)", - "javax.servlet-api:3.1.0 dependency with <scope>provided</scope> in pom.xml", - "weblogic.xml at META-INF/weblogic.xml with WorkManager configuration", - "8 servlet implementations: AuthServlet, MainServlet, UploadServlet, etc.", - "5 filter implementations including CharacterEncodingFilter and AuthenticationFilter", - "WebLogic-specific API usage: weblogic.jms.* and weblogic.servlet.*" - ], - "values": [ - "Servlet API 3.1", - "Java EE 7", - "Oracle WebLogic 12c (minimum)", - "8 servlets, 5 filters", - "WebLogic WorkManager 'default-workmanager'", - "WAR packaging for external deployment" - ] - }, - "execution_time_seconds": 28.4, - "timestamp": "2026-02-28T10:22:38Z" -} -``` diff --git a/.github/skills/fact-startup-instrumentation/SKILL.md b/.github/skills/fact-startup-instrumentation/SKILL.md deleted file mode 100644 index 044800ba3..000000000 --- a/.github/skills/fact-startup-instrumentation/SKILL.md +++ /dev/null @@ -1,371 +0,0 @@ ---- -name: fact-startup-instrumentation -description: Analyze startup instrumentation (logging, telemetry, AOP) ---- - -# Startup Instrumentation Analysis - -## Purpose -Detect and analyze logging frameworks, telemetry/APM tools, and aspect-oriented programming (AOP) components initialized at application startup. - -## Analysis Strategy - -This SKILL searches for configuration files, startup code, and dependency declarations to identify instrumentation frameworks. - -## Analysis Steps - -### 1. Logging Framework Detection - -**Search for Logging Configuration Files:** -```bash -# Use Glob to find config files -Glob patterns: -- **/logback.xml, **/logback-spring.xml -- **/log4j2.xml, **/log4j2.yml, **/log4j.properties -- **/appsettings*.json (for Serilog/NLog in .NET) -- **/logging.conf, **/logging.yaml (Python) -- **/winston.config.js (Node.js) -``` - -**Search for Logging Dependencies:** -```bash -# Use Grep to find logging libraries -Pattern: "logback|log4j2|slf4j|serilog|nlog|ilogger|winston|bunyan|pino|logging\.getLogger" -Files: **/pom.xml, **/build.gradle, **/*.csproj, **/package.json, **/requirements.txt, **/Gemfile -``` - -**Search for Logger Initialization in Code:** -```bash -Pattern: "LoggerFactory|ILogger|getLogger|Logger\.getLogger|createLogger|logging\.basicConfig" -Files: **/Program.cs, **/Startup.cs, **/Main.java, **/*Application.java, **/app.py, **/main.py, **/server.js, **/app.js -``` - -### 2. Telemetry & APM Detection - -**Application Insights (.NET/Java):** -```bash -Pattern: "applicationinsights|Microsoft\.ApplicationInsights|TelemetryClient" -Files: **/*.csproj, **/pom.xml, **/appsettings.json, **/ApplicationInsights.config -``` - -**OpenTelemetry (Cross-platform):** -```bash -Pattern: "opentelemetry|otel|TracerProvider|MeterProvider" -Files: **/pom.xml, **/build.gradle, **/*.csproj, **/package.json, **/requirements.txt -``` - -**New Relic:** -```bash -Pattern: "newrelic|New Relic" -Files: **/newrelic.yml, **/newrelic.config, **/newrelic.js -``` - -**Datadog:** -```bash -Pattern: "datadog|dd-trace|ddtrace" -Files: **/pom.xml, **/package.json, **/requirements.txt, **/Gemfile -``` - -**Dynatrace:** -```bash -Pattern: "dynatrace|oneagent" -Files: **/*.config, **/dockerfile, **/deployment.yaml -``` - -**Elastic APM:** -```bash -Pattern: "elastic-apm|ElasticApm" -Files: **/pom.xml, **/*.csproj, **/package.json, **/requirements.txt -``` - -### 3. Aspect-Oriented Programming (AOP) Detection - -**Spring AOP (Java):** -```bash -Pattern: "@Aspect|@Before|@After|@Around|spring-aop|aspectjweaver" -Files: **/*.java, **/pom.xml, **/build.gradle -``` - -**PostSharp (.NET):** -```bash -Pattern: "PostSharp|[MethodInterception]|[OnMethodBoundary]" -Files: **/*.cs, **/*.csproj -``` - -**AspectJ:** -```bash -Pattern: "aspectj|@Pointcut|@Aspect" -Files: **/*.java, **/aop.xml, **/pom.xml -``` - -### 4. Startup Code Analysis - -**Check Main Entry Points:** -```bash -# Use Glob to find entry points -Patterns: -- **/Program.cs (ASP.NET Core) -- **/Startup.cs (ASP.NET Core) -- **/Main.java, **/*Application.java (Spring Boot) -- **/app.py, **/main.py, **/__init__.py (Python) -- **/server.js, **/app.js, **/index.js (Node.js) -``` - -**Analyze Startup Configuration:** -```bash -# Read entry point files and check for: -- Logger configuration: builder.Logging.Add*, LogManager.Setup() -- Telemetry setup: services.AddApplicationInsightsTelemetry() -- AOP configuration: services.EnableAspectOrientedProgramming() -``` - -## Framework-Specific Patterns - -### Java/Spring Boot - -**Logback:** -```xml -<!-- logback-spring.xml --> -<configuration> - <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> - ... -``` -```java -// Code pattern -private static final Logger logger = LoggerFactory.getLogger(Application.class); -``` - -**Log4j2:** -```xml -<!-- log4j2.xml --> -<Configuration status="WARN"> - <Appenders> -``` -```java -private static final Logger logger = LogManager.getLogger(Application.class); -``` - -### .NET/ASP.NET Core - -**Serilog:** -```csharp -// Program.cs -Log.Logger = new LoggerConfiguration() - .WriteTo.Console() - .WriteTo.File("logs/log.txt") - .CreateLogger(); -``` -```json -// appsettings.json -"Serilog": { - "Using": ["Serilog.Sinks.Console", "Serilog.Sinks.File"] -} -``` - -**NLog:** -```csharp -// Program.cs -builder.Logging.ClearProviders(); -builder.Host.UseNLog(); -``` -```xml -<!-- nlog.config --> -<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"> -``` - -**ILogger (Built-in):** -```csharp -builder.Logging.AddConsole(); -builder.Logging.AddDebug(); -builder.Logging.AddApplicationInsights(); -``` - -### Python - -**Logging Module:** -```python -import logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) -``` - -**Loguru:** -```python -from loguru import logger -logger.add("file.log", rotation="500 MB") -``` - -### Node.js - -**Winston:** -```javascript -const winston = require('winston'); -const logger = winston.createLogger({ - transports: [ - new winston.transports.Console(), - new winston.transports.File({ filename: 'combined.log' }) - ] -}); -``` - -**Pino:** -```javascript -const pino = require('pino'); -const logger = pino(); -``` - -## Analysis Decision Logic - -1. **Check for configuration files** → High confidence if config exists -2. **Check for dependencies** → Medium confidence -3. **Check for code usage** → Confirms framework is actively used -4. **Check for multiple frameworks** → List all detected - -## Confidence Levels - -- **High**: Configuration file + dependency + code usage detected -- **Medium**: Dependency + code usage (no explicit config) -- **Low**: Only dependency found (may not be actively used) - -## Output Format - -After analysis, call the MCP tool: - -```javascript -write_assessment_result({ - resultJson: JSON.stringify({ - input_name: "Startup Instrumentation", - analysis_method: "Hybrid", // Code + LLM analysis - status: "success", // or "not_applicable" or "failed" - result: { - finding: "Serilog with Application Insights and custom AOP interceptors", - confidence: "high", // high, medium, or low - evidence: [ - "Found appsettings.json with Serilog configuration (Console, File, Application Insights sinks)", - "Program.cs configures Serilog at line 12: Log.Logger = new LoggerConfiguration()...", - "ApplicationInsights.config found with instrumentation key", - "Custom AOP: Found 3 MethodInterceptionAspect classes for logging, caching, and validation" - ], - values: [ - "Serilog 3.1.1", - "Application Insights", - "Custom AOP (MethodInterception)" - ], - instrumentation_details: { - logging_framework: "Serilog", - logging_version: "3.1.1", - log_sinks: ["Console", "File", "ApplicationInsights"], - telemetry_provider: "Application Insights", - aop_framework: "Custom (MethodInterception)", - startup_file: "Program.cs" - } - }, - execution_time_seconds: 2.8, - timestamp: new Date().toISOString() - }), - assessmentDir: variables.assessment_dir -}); -``` - -## Multiple Framework Examples - -### Example 1: Spring Boot with Logback + Elastic APM + Spring AOP -```json -{ - "finding": "Logback with Elastic APM and Spring AOP", - "confidence": "high", - "evidence": [ - "logback-spring.xml found with RollingFileAppender", - "pom.xml includes elastic-apm-agent 1.39.0", - "Found 8 @Aspect classes for cross-cutting concerns", - "Application.java initializes APM agent at startup" - ], - "values": ["Logback", "Elastic APM 1.39.0", "Spring AOP", "AspectJ"] -} -``` - -### Example 2: Python with Loguru + OpenTelemetry -```json -{ - "finding": "Loguru with OpenTelemetry", - "confidence": "medium", - "evidence": [ - "requirements.txt includes loguru==0.7.0", - "app.py configures loguru with rotation and retention", - "Found opentelemetry-api in requirements.txt", - "No explicit OpenTelemetry initialization code found" - ], - "values": ["Loguru 0.7.0", "OpenTelemetry (partial)"] -} -``` - -### Example 3: Node.js with Winston + Datadog -```json -{ - "finding": "Winston with Datadog APM", - "confidence": "high", - "evidence": [ - "winston.config.js configures Console, File, and HTTP transports", - "package.json includes winston@3.11.0 and dd-trace@4.23.0", - "server.js imports and initializes dd-trace at line 1", - "Found custom Winston format for JSON structured logging" - ], - "values": ["Winston 3.11.0", "Datadog APM 4.23.0"] -} -``` - -## Not Applicable Scenarios - -Return `status: "not_applicable"` if: -- No logging framework detected (using only print/console.log) -- Project is a library without startup entry point -- CLI tool with minimal logging requirements - -Example: -```json -{ - "input_name": "Startup Instrumentation", - "analysis_method": "Code", - "status": "not_applicable", - "result": { - "finding": "No structured logging framework detected", - "confidence": "high", - "evidence": [ - "No logging configuration files found", - "No logging dependencies in package.json", - "Only console.log statements found in code" - ], - "values": [] - }, - "execution_time_seconds": 1.2, - "timestamp": "2026-03-01T01:00:00Z" -} -``` - -## Error Handling - -Return `status: "failed"` if: -- Unable to access project files -- Critical analysis error occurred -- Timeout during file search - -Include error details: -```json -{ - "status": "failed", - "result": { - "finding": "Analysis failed", - "confidence": "low", - "evidence": ["Error: Permission denied reading configuration files"], - "values": [] - } -} -``` - -## Notes - -- Applications often use multiple logging/telemetry tools (e.g., Serilog + Application Insights) -- List all detected frameworks in `values` array -- Prioritize most prominent framework in `finding` -- AOP frameworks are often used for logging interception - include in analysis -- Check both code and configuration for complete picture diff --git a/.github/skills/fact-system-packages/SKILL.md b/.github/skills/fact-system-packages/SKILL.md deleted file mode 100644 index c315120b6..000000000 --- a/.github/skills/fact-system-packages/SKILL.md +++ /dev/null @@ -1,99 +0,0 @@ ---- -name: fact-system-packages -description: Identify system packages installed in container (nginx, curl, git, etc.) ---- - -# System Packages Analysis - -## Purpose -Identify OS-level system packages installed in the container image through package manager commands. - -## Target Files/Locations -- **/Dockerfile, **/Containerfile - -## Example Patterns -- `RUN apt-get install nginx curl git` -- `RUN apk add --no-cache ca-certificates tzdata` -- `RUN yum install -y postgresql-client` - -## Analysis Steps - -### 1. Find Package Installation Commands -``` -Use Grep: "apt-get install|apt install|apk add|yum install|dnf install" -Files: **/Dockerfile -Context: -A 5 (capture multi-line installs) -``` - -### 2. Extract Package Names -``` -Parse package manager commands: -- apt: after "install" keyword -- apk: after "add" keyword -- yum/dnf: after "install" keyword - -Handle flags: --no-cache, -y, --no-install-recommends -Handle line continuations with \ -``` - -### 3. Categorize Packages -``` -Group by purpose: -- Web servers: nginx, apache2 -- SSL/Certificates: ca-certificates, openssl -- Development tools: git, curl, wget, vim -- Database clients: postgresql-client, mysql-client -- Build tools: gcc, make, build-essential -- Utilities: tzdata, bash, coreutils -``` - -### 4. Count Total Packages -``` -Sum all packages across all RUN commands -Note if cleanup commands present (apt-get clean, rm -rf /var/lib/apt/lists/*) -``` - -## Confidence Determination - -### High Confidence -- ✅ Package install commands clearly visible -- ✅ Package names explicitly listed -- **Example**: "15 system packages installed: nginx, curl, git, ca-certificates, tzdata, etc." - -### Medium Confidence -- ⚠️ Some packages via install scripts -- **Example**: "Packages installed via apt but list partially in script files" - -### Low Confidence -- ⚠️ Base image includes packages, additions unclear -- **Example**: "Base image may include packages, specific additions unclear" - -### Not Applicable -- ❌ No container or minimal base (scratch, distroless) -- **Example**: "Uses scratch base image with no package manager" - -## Output Format - -```json -{ - "input_name": "System Packages", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Packages summary}", - "confidence": "high|medium|low", - "evidence": [ - "{Package manager commands}", - "{Package count}", - "{Installation patterns}" - ], - "values": [ - "{Package names list}", - "{Categories: web, ssl, dev tools, etc.}", - "{Package manager: apt, apk, yum}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-testing-framework/SKILL.md b/.github/skills/fact-testing-framework/SKILL.md deleted file mode 100644 index 97d5e5b9d..000000000 --- a/.github/skills/fact-testing-framework/SKILL.md +++ /dev/null @@ -1,139 +0,0 @@ ---- -name: fact-testing-framework -description: Analyze testing tools and frameworks used in the project ---- - -# Testing Framework Analysis - -## Purpose -Detect testing frameworks and tools used in the codebase. - -## Target Files/Locations -- **/pom.xml, **/build.gradle, **/build.gradle.kts (Java) -- **/*.csproj (C#/.NET) -- **/package.json (Node.js) -- **/requirements.txt, **/requirements-dev.txt, **/setup.py, **/pyproject.toml (Python) -- **/go.mod (Go) -- **/test/**/*.{java,cs,py,js,ts}, **/*Test.{java,cs}, **/*Tests.cs, **/*.test.{js,ts}, **/*.spec.{js,ts}, **/test_*.py, **/*_test.go - -## Analysis Steps - -### 1. Check Java Testing Frameworks (Maven/Gradle) -``` -Use Grep: "junit-jupiter|<artifactId>junit</artifactId>|testng|mockito" -Files: **/pom.xml, **/build.gradle, **/build.gradle.kts - -Map findings: -- junit-jupiter → JUnit 5 -- <artifactId>junit</artifactId> → JUnit 4 -- testng → TestNG -- mockito → Mockito -``` - -### 2. Check .NET Testing Frameworks -``` -Use Grep: "xunit|nunit|MSTest" -Files: **/*.csproj - -Map findings: -- xunit → xUnit -- nunit → NUnit -- MSTest → MSTest -``` - -### 3. Check Node.js Testing Frameworks -``` -Use Grep: "\"jest\"|\"@types/jest\"|\"mocha\"|\"@types/mocha\"|\"chai\"|\"jasmine\"|\"vitest\"|\"@vitest\"" -Files: **/package.json - -Map findings: -- jest / @types/jest → Jest -- mocha / @types/mocha → Mocha -- chai / @types/chai → Chai -- jasmine / @types/jasmine → Jasmine -- vitest / @vitest → Vitest -``` - -### 4. Check Python Testing Frameworks -``` -Use Grep: "pytest|unittest|nose" -Files: **/requirements.txt, **/requirements-dev.txt, **/setup.py, **/pyproject.toml - -Map findings: -- pytest → pytest -- unittest → unittest -- nose → nose -``` - -### 5. Check Go Testing Frameworks -``` -Use Grep: "testify|ginkgo" -Files: **/go.mod - -Map findings: -- testify → testify -- ginkgo → Ginkgo -``` - -### 6. Count Test Files -``` -Use Glob to find test files: -- **/*Test.java, **/*Test.cs, **/*Tests.cs -- **/*.test.js, **/*.test.ts, **/*.spec.js, **/*.spec.ts -- **/test_*.py, **/*_test.go - -Count matching files for evidence -``` - -### 7. Analyze Test Patterns in Source -``` -Use Grep: "@Test|@TestMethod|\\[Fact\\]|\\[Theory\\]|describe\\(|it\\(|test\\(" -Files: **/*.{java,cs,js,ts,py} -Context: -B 1 -A 2 - -Check for common test annotations/decorators -``` - -## Confidence Determination - -### High Confidence -- ✅ Testing framework dependencies found in build files -- ✅ Test files present with matching annotations -- **Example**: "JUnit 5, Mockito detected in pom.xml; Found 23 test files" - -### Medium Confidence -- ⚠️ Test files found but no explicit framework dependency -- **Example**: "Test files found, framework inferred from annotations" - -### Low Confidence -- ⚠️ Few test files, no framework dependencies -- **Example**: "2 test-like files found, no framework detected" - -### Not Applicable -- ❌ No test files or testing dependencies -- **Example**: "No testing frameworks detected" - -## Output Format - -```json -{ - "input_name": "Testing Framework", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Frameworks summary}", - "confidence": "high|medium|low", - "evidence": [ - "{Build file framework references}", - "{Test file counts}", - "{Test annotation patterns}" - ], - "values": [ - "{Detected frameworks: JUnit 5, Mockito, xUnit, Jest, etc.}", - "{Test file count}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-version-information/SKILL.md b/.github/skills/fact-version-information/SKILL.md deleted file mode 100644 index ee5894325..000000000 --- a/.github/skills/fact-version-information/SKILL.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -name: fact-version-information -description: Extract application version from build files and configuration ---- - -# Version Information Analysis - -## Purpose -Identify the application version number from build descriptors, manifests, and configuration files. - -## Target Files/Locations -- **/pom.xml (<version>) -- **/*.csproj (<Version>) -- **/package.json (version field) -- **/build.gradle (version property) -- **/application.{properties,yml} (info.app.version) -- **/AssemblyInfo.cs ([assembly: AssemblyVersion]) - -## Example Patterns -- `<version>1.2.3</version>` (Maven) -- `"version": "2.0.0-beta"` (Node.js) -- `<Version>1.5.0</Version>` (.NET) -- `version = "3.1.4"` (Gradle) - -## Analysis Steps - -### 1. Check Build File Versions -``` -Maven (pom.xml): -Use Grep: "<version>" -Extract first non-parent version - -Gradle (build.gradle): -Use Grep: "^version\\s*=" -Extract: version = '1.2.3' - -Node.js (package.json): -Use Read and parse: { "version": "1.2.3" } - -.NET (*.csproj): -Use Grep: "<Version>" -Extract: <Version>1.2.3</Version> -``` - -### 2. Check Assembly Info -``` -.NET: -Use Grep: "AssemblyVersion|AssemblyFileVersion" -Files: **/AssemblyInfo.cs -Extract: [assembly: AssemblyVersion("1.2.3.4")] -``` - -### 3. Check Application Configuration -``` -Use Grep: "version:|app\\.version|info\\.app\\.version" -Files: **/application.{properties,yml}, **/appsettings.json -``` - -### 4. Check Git Tags -``` -Use Bash: git describe --tags --abbrev=0 -Latest tag often represents version -``` - -## Confidence Determination - -### High Confidence -- ✅ Version explicitly in build file -- ✅ Semantic versioning format (X.Y.Z) -- **Example**: "Application version: 1.2.3 from pom.xml and package.json" - -### Medium Confidence -- ⚠️ Version found but format unusual -- ⚠️ Snapshot/development version -- **Example**: "Version: 2.0.0-SNAPSHOT (development version)" - -### Low Confidence -- ⚠️ No version in files, inferred from git -- **Example**: "Version unclear, git tag suggests 1.0.0" - -### Not Applicable -- ❌ Versioning not used -- **Example**: "No version information found in project" - -## Output Format - -```json -{ - "input_name": "Version Information", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Version summary}", - "confidence": "high|medium|low", - "evidence": [ - "{Source file and location}", - "{Version format}", - "{Git tag if applicable}" - ], - "values": [ - "{Version number: X.Y.Z}", - "{Version type: release, snapshot, beta}", - "{Build/patch number if applicable}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-volume-mounts/SKILL.md b/.github/skills/fact-volume-mounts/SKILL.md deleted file mode 100644 index 65d8a57dd..000000000 --- a/.github/skills/fact-volume-mounts/SKILL.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -name: fact-volume-mounts -description: Identify persistent volume configurations for data storage ---- - -# Volume Mounts Analysis - -## Purpose -Identify persistent storage volumes configured for data persistence, configuration, and logs. - -## Target Files/Locations -- **/Dockerfile** (VOLUME instruction) -- **/docker-compose*.yml** (volumes section) -- **/k8s/**/*.yaml** (volumeMounts, volumes, PersistentVolumeClaim) - -## Example Patterns -- `VOLUME /data /logs` -- `volumes: - ./data:/app/data` -- `mountPath: /var/lib/postgresql/data` - -## Analysis Steps - -### 1. Check Dockerfile VOLUME -``` -Use Grep: "^VOLUME\\s+" -Files: **/Dockerfile -Extract volume paths -``` - -### 2. Analyze docker-compose Volumes -``` -Use Read: **/docker-compose*.yml -Look for volumes: section (both top-level and per-service) -Types: bind mounts, named volumes, tmpfs -``` - -### 3. Check Kubernetes Volumes -``` -Use Grep: "volumeMounts:|volumes:|persistentVolumeClaim" -Files: **/k8s/**/*.yaml -Context: -B 1 -A 5 -``` - -### 4. Categorize Volume Purposes -``` -Group by type: -- Data: /data, /var/lib/postgresql, /var/lib/mysql -- Logs: /logs, /var/log -- Config: /etc/config, /app/config -- Temp: /tmp, /var/tmp -``` - -## Confidence Determination - -### High Confidence -- ✅ Volumes explicitly configured -- ✅ Mount paths and purposes clear -- **Example**: "3 volumes: /data for database, /logs for application logs, /config for runtime config" - -### Medium Confidence -- ⚠️ VOLUME declared but mount points unclear -- **Example**: "Dockerfile declares VOLUME /data but docker-compose doesn't mount it" - -### Low Confidence -- ⚠️ No explicit volumes but app may use storage -- **Example**: "No volumes configured, data likely ephemeral" - -### Not Applicable -- ❌ Stateless application -- **Example**: "Stateless API with no persistent storage" - -## Output Format - -```json -{ - "input_name": "Volume Mounts", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Volumes summary}", - "confidence": "high|medium|low", - "evidence": [ - "{Dockerfile VOLUME}", - "{docker-compose volumes}", - "{K8s PVC/volumes}" - ], - "values": [ - "{Volume paths}", - "{Purposes: data, logs, config}", - "{Volume types: bind, named, PVC}" - ] - }, - "execution_time_seconds": {elapsed}, - "timestamp": "{ISO 8601}" -} -``` diff --git a/.github/skills/fact-xml-configs/SKILL.md b/.github/skills/fact-xml-configs/SKILL.md deleted file mode 100644 index e49ea6bcf..000000000 --- a/.github/skills/fact-xml-configs/SKILL.md +++ /dev/null @@ -1,298 +0,0 @@ ---- -name: fact-xml-configs -description: Analyze XML configuration files usage in the application ---- - -# XML Configuration Files Analysis - -## Purpose -Identify and catalog XML configuration files used by the application, including Spring context files, Hibernate configurations, web.xml, and other framework-specific XML files. This helps understand configuration management approach and migration requirements. - -## Target Files/Locations -- **Spring Framework**: **/applicationContext*.xml, **/spring-*.xml, **/*-context.xml, **/beans.xml -- **Hibernate ORM**: **/hibernate.cfg.xml, **/*.hbm.xml -- **Java EE/Jakarta EE**: **/web.xml, **/ejb-jar.xml, **/persistence.xml -- **MyBatis**: **/mybatis-config.xml, **/*-mapper.xml -- **Log4j**: **/log4j.xml, **/log4j2.xml -- **Maven**: **/pom.xml -- **Build configs**: **/build.xml (Ant), **/ivy.xml -- **Application configs**: **/config/**/*.xml, **/conf/**/*.xml - -## Example Patterns to Search -- Spring XML config with bean definitions -- Hibernate mapping files (.hbm.xml) -- MyBatis SQL mapper files -- web.xml servlet configurations -- persistence.xml JPA configurations -- log4j.xml or log4j2.xml logging configurations - -## Analysis Steps - -### 1. Search for Spring XML Configuration Files -``` -Use Glob tool to find Spring config files: -- **/applicationContext*.xml -- **/spring-*.xml -- **/*-context.xml -- **/beans.xml -- **/META-INF/spring/**/*.xml - -For each file found: -- Use Read tool to examine first 50 lines -- Check for <beans> root element -- Identify bean definitions, imports, property placeholders -- Note component-scan or annotation-config presence -``` - -### 2. Search for Hibernate Configuration Files -``` -Use Glob tool to find Hibernate files: -- **/hibernate.cfg.xml -- **/*.hbm.xml - -For each file: -- Read to identify database dialect -- Check for entity mappings -- Note session factory configuration -- Identify connection pool settings -``` - -### 3. Search for Java EE/Jakarta EE Deployment Descriptors -``` -Use Glob tool to find Java EE files: -- **/WEB-INF/web.xml -- **/META-INF/ejb-jar.xml -- **/META-INF/persistence.xml -- **/META-INF/application.xml - -Analyze each: -- Servlet/filter configurations (web.xml) -- JPA entity configurations (persistence.xml) -- EJB declarations (ejb-jar.xml) -``` - -### 4. Search for MyBatis Configuration Files -``` -Use Glob tool: -- **/mybatis-config.xml -- **/*-mapper.xml -- **/sqlmap/**/*.xml - -Check for: -- SQL mapper namespaces -- Select/insert/update/delete statements -- Result maps and parameter maps -``` - -### 5. Search for Logging XML Configurations -``` -Use Glob tool: -- **/log4j.xml -- **/log4j2.xml -- **/logback.xml - -Analyze: -- Appender configurations -- Logger level settings -- Output patterns -``` - -### 6. Count and Categorize All XML Files -``` -Use Bash tool to count XML files: -find . -type f -name "*.xml" -not -path "*/target/*" -not -path "*/.git/*" | wc -l - -Use Grep to identify XML configuration files (vs data files): -- Pattern: "<beans|<configuration|<hibernate-configuration|<web-app|<persistence" -- Files: **/*.xml -- Output mode: files_with_matches - -Categorize by framework: -- Spring: beans, context namespaces -- Hibernate: hibernate-configuration, hibernate-mapping -- Java EE: web-app, ejb-jar, persistence-unit -- MyBatis: mapper namespace -- Logging: log4j:configuration, configuration (logback) -``` - -### 7. Analyze XML Complexity and Size -``` -For each significant XML file: -- Use Read to get line count -- Check for external entity references -- Identify property placeholders (${...}) -- Note XSD/DTD schema references -- Check for profiles or conditional configurations -``` - -## Confidence Determination - -### High Confidence Criteria -Clear evidence of XML configuration usage with detailed findings: -- ✅ Multiple XML configuration files identified with specific purposes -- ✅ Framework-specific XML files present (Spring, Hibernate, etc.) -- ✅ Valid XML structure confirmed with proper root elements -- ✅ Bean/entity/mapper definitions clearly visible -- ✅ Relationship between files understood (imports, includes) - -**Examples**: -- "Found 23 Spring XML config files with 450+ bean definitions across applicationContext.xml and imported files" -- "Hibernate configuration with 15 .hbm.xml entity mapping files and hibernate.cfg.xml" -- "Web application with web.xml (Servlet 3.1) and 8 Spring context XML files" - -### Medium Confidence Criteria -Partial evidence or incomplete information: -- ⚠️ XML files found but unclear purpose or usage -- ⚠️ Config files present but may be legacy/unused -- ⚠️ XML files exist alongside annotation-based config (hybrid approach) -- ⚠️ Limited XML usage (only logging or build configs) -- ⚠️ XML files in resources but no clear loading mechanism - -**Examples**: -- "Spring XML files found but @Configuration classes also present (hybrid setup)" -- "Hibernate .hbm.xml files exist but JPA annotations also used" -- "XML files present but timestamps suggest not recently modified" - -### Low Confidence Criteria -Weak or ambiguous evidence: -- ⚠️ Only build tool XML files (pom.xml, build.xml) -- ⚠️ No framework-specific XML configurations -- ⚠️ XML files are data files, not configuration -- ⚠️ Test resources only, no production configs -- ⚠️ Commented-out or example XML files - -**Examples**: -- "Only pom.xml found, no application XML configs" -- "XML files in test resources only, production uses properties/YAML" -- "Sample XML files in documentation directory, not active configs" - -### Not Applicable Criteria -When XML configuration analysis doesn't apply: -- ❌ Pure annotation-based configuration (Spring Boot @Configuration) -- ❌ Configuration via properties/YAML files only -- ❌ Non-Java application (.NET, Node.js, Python) -- ❌ Library project with no configuration requirements -- ❌ Modern application using Java Config exclusively - -**Examples**: -- "Spring Boot application using only @Configuration classes and application.yml" -- ".NET Core application, XML config analysis not applicable" -- "Node.js application with JSON configuration only" - -## Output Format - -**CRITICAL**: Use the `write_assessment_result` tool (not just output JSON text). - -```json -{ - "input_name": "XML Configs", - "analysis_method": "LLM", - "status": "success|not_applicable", - "result": { - "finding": "{Clear 1-2 sentence summary of XML configuration usage}", - "confidence": "high|medium|low", - "evidence": [ - "{Number and types of XML files found}", - "{Specific file paths for major configs}", - "{Framework identifications from XML content}", - "{Configuration patterns observed}" - ], - "values": [ - "{Framework: Spring, Hibernate, MyBatis, etc.}", - "{File count by type}", - "{Key configuration file names}", - "{Schema versions or namespaces}" - ] - }, - "execution_time_seconds": {elapsed_time}, - "timestamp": "{ISO 8601 timestamp}" -} -``` - -**Finding Examples**: -- ✅ Good: "Application uses Spring XML configuration with 18 context files defining 250+ beans, including applicationContext.xml as root config" -- ✅ Good: "Hibernate-based persistence with hibernate.cfg.xml and 12 .hbm.xml entity mapping files for database layer" -- ✅ Good: "Web application with web.xml (Servlet 3.0), 5 Spring XML contexts, and MyBatis mapper files for SQL" -- ✅ Good: "No XML configuration detected - application uses Spring Boot with annotation-based @Configuration classes" -- ❌ Bad: "XML files found" -- ❌ Bad: "Configuration exists" - -**Evidence Examples**: -- ✅ Good: "applicationContext.xml at src/main/resources/ with 45 bean definitions and 3 imported context files" -- ✅ Good: "15 Hibernate mapping files (*.hbm.xml) in src/main/resources/mappings/ for entity persistence" -- ✅ Good: "web.xml at WEB-INF/ defines 8 servlets and 12 filters with Spring DispatcherServlet" -- ✅ Good: "23 MyBatis mapper XML files in resources/mappers/ with SQL definitions" -- ❌ Bad: "Found XML files in project" -- ❌ Bad: "Spring configuration present" - -## Error Handling - -### 1. No XML Configuration Found -- Report finding as "No XML configuration files detected" -- Set confidence to high if thorough search confirmed absence -- Note alternative config approaches if detected (annotations, properties, YAML) - -### 2. Mixed Configuration Approaches -- Report both XML and alternative approaches found -- Set confidence to medium -- List what's configured via XML vs annotations/properties -- Example: "Hybrid approach: Spring XML for legacy beans, @Configuration for new services" - -### 3. Too Many XML Files -- If >100 XML files found, categorize and sample -- Provide statistics by category -- Focus detailed analysis on framework config files -- Note if many are data files vs configuration files - -### 4. Tool Failures -- If Glob returns too many results, refine with more specific patterns -- If Read fails on corrupted XML, note the file but continue -- After 3 retries on critical operations, report partial results with caveats - -### 5. Invalid/Malformed XML -- Note files that appear to be XML but fail parsing -- Don't let malformed files block analysis -- Report count of valid vs invalid XML files found - -## Example Complete Analysis - -**Scenario**: Java Spring MVC application with XML-based configuration - -**Steps Executed**: -1. Glob for Spring XML: Found 18 files (applicationContext.xml + 17 imported) -2. Read applicationContext.xml: Confirmed Spring 4.3 with bean definitions -3. Glob for Hibernate: Found hibernate.cfg.xml and 12 .hbm.xml files -4. Glob for Java EE: Found web.xml in WEB-INF/ -5. Count total XML: 45 XML files in project (excluding target/) -6. Categorized: 18 Spring, 13 Hibernate, 1 Java EE, 8 MyBatis, 2 logging, 3 Maven - -**Result**: -```json -{ - "input_name": "XML Configs", - "analysis_method": "LLM", - "status": "success", - "result": { - "finding": "Application heavily uses XML configuration with 18 Spring context files, 13 Hibernate mappings, and Java EE web.xml for a traditional XML-based architecture", - "confidence": "high", - "evidence": [ - "applicationContext.xml at src/main/resources/ with <beans> root and 45 bean definitions", - "18 Spring XML files with imports: data-context.xml, security-context.xml, service-context.xml, etc.", - "hibernate.cfg.xml and 12 entity mapping files (*.hbm.xml) in resources/mappings/", - "web.xml at WEB-INF/web.xml defines DispatcherServlet and 8 filters (Servlet API 3.0)", - "8 MyBatis mapper XML files in resources/mappers/ with SQL queries", - "log4j.xml configuration for logging with 5 appenders" - ], - "values": [ - "Spring Framework (XML-based config, Spring 4.3 schema)", - "Hibernate ORM (12 .hbm.xml entity mappings)", - "Java EE Servlet 3.0 (web.xml)", - "MyBatis (8 mapper files)", - "45 total XML configuration files", - "Log4j XML configuration" - ] - }, - "execution_time_seconds": 32.8, - "timestamp": "2026-02-28T10:18:15Z" -} -``` diff --git a/.github/skills/generate-report-html/SKILL.md b/.github/skills/generate-report-html/SKILL.md deleted file mode 100644 index a760bd87b..000000000 --- a/.github/skills/generate-report-html/SKILL.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -name: generate-report-html -description: Generate report.html from assessment report data by running the generate-report-html.py script ---- - -# Generate Report HTML - -Generate a self-contained `report.html` file from the assessment output by running the included Python script. - -## Execution - -Run the following command: - -```bash -python .github/modernize/assessment/engines/generate-report-html.py .github/modernize/assessment/reports/report-{reportId} -``` - -If the script is not at that path, look for it in the skill's own directory or copy it from there. The script file is `generate-report-html.py` bundled alongside this skill. - -Alternatively, if the script is available in the current working directory or skill resources: - -```bash -python generate-report-html.py .github/modernize/assessment/reports/report-{reportId} -``` - -## What the script does - -1. Reads `report.json` (Java/.NET) or `js-assessment-report.md` (JS/TS) from the report directory -2. Reads fact files from the `facts/` subdirectory (if present) -3. Generates `report.html` in the same directory - -## Prerequisites - -- Python 3 must be available in the environment -- The versioned report directory must already exist with `report.json` or the JS/TS report file -- Fact files should already be copied to the `facts/` subdirectory (if architecture analysis was enabled) - -## Input - -The script takes one argument: the path to the versioned report directory, e.g.: -`.github/modernize/assessment/reports/report-20240615143052` - -## Output - -`report.html` is written to the same directory as the input report. - -## Fallback - -If Python is not available or the script fails, you may generate report.html manually following the same structure as the local CLI renderer produces. The key requirements are: -- Self-contained HTML with embedded CSS and JavaScript -- SVG donut charts for issue summary -- Interactive expandable issue rows with file locations -- Fact tabs with Mermaid diagram support -- No "Back to aggregate dashboard" link diff --git a/.github/skills/generate-report-html/generate-report-html.py b/.github/skills/generate-report-html/generate-report-html.py deleted file mode 100644 index 122ee5070..000000000 --- a/.github/skills/generate-report-html/generate-report-html.py +++ /dev/null @@ -1,1410 +0,0 @@ -#!/usr/bin/env python3 -""" -Generates report.html from report.json (Java/.NET) or js-assessment-report.md (JS/TS). - -Usage: - python generate_report_html.py /path/to/.github/modernize/assessment/reports/report-{id} -""" - -import json -import math -import os -import re -import sys -from html import escape -from pathlib import Path - -# ── CSS (exact copy from reference) ────────────────────────────────────────── -CSS = r""":root { - --bg-primary: #ffffff; - --bg-card: #f8f9fa; - --bg-page: #f0f1f3; - --text-primary: #24292f; - --text-secondary: #57606a; - --text-muted: #6b7280; - --border-color: #d0d7de; - --border-light: #e1e4e8; - --link-color: #2563eb; - --color-mandatory: #E3008C; - --color-potential: #637CEF; - --color-optional: #A19F9D; - --font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Arial, sans-serif; - --font-mono: 'Cascadia Code', 'Consolas', 'Courier New', monospace; -} -* { box-sizing: border-box; margin: 0; padding: 0; } -body { font-family: var(--font-family); font-size: 13px; color: var(--text-primary); background: var(--bg-primary); line-height: 1.5; margin: 0; padding: 0; } -a { color: var(--link-color); text-decoration: none; } -a:hover { text-decoration: underline; } - -/* ── Page layout ───────────────────────────────────────────── */ -.main { margin: 0; padding: 24px 32px; background: var(--bg-primary); } -.back-link { color: var(--text-muted); font-size: 13px; margin-bottom: 16px; display: block; } -h1 { font-weight: 600; font-size: 20px; color: var(--text-primary); margin-bottom: 20px; } - -/* ── Report cards (matches VS Code .vscode-report-card) ──── */ -.report-card { background: var(--bg-card); border: 1px solid var(--border-light); border-radius: 4px; padding: 12px; margin-bottom: 20px; } -.report-card h2 { font-size: 14px; font-weight: 600; margin: 0; color: var(--text-primary); } -.report-card-body { margin-top: 16px; } - -/* ── Application Information (two-column) ────────────────── */ -.app-info-container { display: flex; gap: 8px; flex-wrap: wrap; } -.app-info-column { flex: 1; min-width: 200px; display: flex; flex-direction: column; gap: 8px; } -.app-info-row { display: flex; align-items: baseline; } -.app-info-label { width: 136px; flex-shrink: 0; font-weight: 600; color: var(--text-primary); } -.app-info-value { flex: 1; color: var(--text-primary); } - -/* ── Issue Summary (pie charts + legend) ─────────────────── */ -.cards-container { display: flex; align-items: stretch; gap: 20px; flex-wrap: wrap; } -.issue-summary-card { flex: 2; } -.domain-summary-container { display: flex; flex-wrap: wrap; gap: 20px; justify-content: space-around; } -.domain-summary-item { flex: 1; min-width: 120px; text-align: center; padding: 0 16px; border-right: 1px solid var(--border-light); } -.domain-summary-item:last-child { border-right: none; } -.domain-summary-item h3 { font-size: 13px; font-weight: 600; color: var(--text-primary); margin-top: 8px; } -.domain-summary-item .count { font-size: 12px; color: var(--text-secondary); } -.criticality-legend { display: flex; flex-wrap: wrap; gap: 15px; padding-top: 12px; justify-content: center; } -.criticality-legend .legend-item { display: flex; align-items: center; gap: 4px; font-size: 12px; color: var(--text-secondary); } -.legend-swatch { display: inline-block; width: 1em; height: 1em; border-radius: 2px; } - -/* ── Issue list tables ───────────────────────────────────── */ -.issue-section { margin-top: 20px; } -.issue-section h2 { font-size: 14px; font-weight: 600; margin-bottom: 8px; } -.issue-table { width: 100%; border-collapse: collapse; table-layout: fixed; min-width: 600px; } -.issue-table th { text-align: left; text-transform: uppercase; font-weight: 600; font-size: calc(13px * 0.875); padding: 6px 8px; background: var(--bg-card); border-bottom: 1px solid var(--border-color); color: var(--text-secondary); position: sticky; top: 0; z-index: 1; } -.issue-table td { padding: 12px 8px; border-bottom: 1px solid var(--border-light); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; } -.col-issue { width: 55%; } -.col-criticality { width: 25%; } -.col-storypoint { width: 20%; } - -/* ── Target dropdown ─────────────────────────────────────── */ -.target-select-container { margin: 4px 0 16px 0; display: flex; align-items: center; gap: 8px; } -.target-select-container label { font-weight: 600; color: var(--text-primary); font-size: 14px; } -.target-select-container select { font-family: var(--font-family); font-size: 13px; padding: 4px 8px; border: 1px solid var(--border-color); border-radius: 4px; background: var(--bg-primary); color: var(--text-primary); cursor: pointer; } - -/* ── Filter bar ──────────────────────────────────────────── */ -.filter-bar { display: flex; flex-wrap: wrap; gap: 16px; margin: 16px 0 8px 0; align-items: center; } -.multi-select { position: relative; display: inline-block; min-width: 140px; } -.multi-select-btn { font-family: var(--font-family); font-size: 13px; padding: 4px 24px 4px 8px; border: 1px solid var(--border-color); border-radius: 4px; background: var(--bg-primary); color: var(--text-primary); cursor: pointer; width: 100%; text-align: left; position: relative; white-space: nowrap; } -.multi-select-btn::after { content: '\25BE'; position: absolute; right: 8px; top: 50%; transform: translateY(-50%); font-size: 11px; color: var(--text-secondary); } -.multi-select-dropdown { display: none; position: absolute; top: 100%; left: 0; min-width: 100%; background: var(--bg-primary); border: 1px solid var(--border-color); border-radius: 4px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); z-index: 100; margin-top: 2px; } -.multi-select.open .multi-select-dropdown { display: block; } -.multi-select-option { display: flex; align-items: center; gap: 6px; padding: 5px 10px; cursor: pointer; font-size: 13px; white-space: nowrap; } -.multi-select-option:hover { background: var(--bg-secondary); } -.multi-select-option input[type=checkbox] { margin: 0; cursor: pointer; } -.clear-filters { font-size: 12px; color: var(--link-color); cursor: pointer; margin-left: 4px; } - -/* ── Criticality labels (colored square + text) ──────────── */ -.crit-label { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; white-space: nowrap; } -.crit-square { display: inline-block; width: 10px; height: 10px; border-radius: 2px; flex-shrink: 0; } -.crit-square-mandatory { background: var(--color-mandatory); } -.crit-square-potential { background: var(--color-potential); } -.crit-square-optional { background: var(--color-optional); } - -/* ── Expandable rows ─────────────────────────────────────── */ -.expand-btn { background: none; border: none; cursor: pointer; width: 20px; height: 20px; display: inline-flex; align-items: center; justify-content: center; color: var(--text-muted); font-size: 11px; transition: transform 0.15s; padding: 0; vertical-align: middle; flex-shrink: 0; } -.expand-btn.open { transform: rotate(90deg); } -.issue-title-cell { display: flex; align-items: center; gap: 4px; } -.detail-row td { padding: 0; border-bottom: 1px solid var(--border-light); white-space: normal; overflow: visible; text-overflow: clip; } -.detail-content { display: flex; gap: 0; padding: 8px 8px 8px 32px; min-width: 0; } -.file-list { flex: 0 0 50%; min-width: 0; overflow: hidden; } -.file-list table { width: 100%; border-collapse: collapse; } -.file-list th { font-size: calc(13px * 0.875); text-transform: uppercase; font-weight: 600; color: var(--text-secondary); padding: 6px 8px; border-bottom: 1px solid var(--border-color); text-align: left; height: 32px; } -.file-list td { font-size: 13px; padding: 6px 8px; border-bottom: 1px solid var(--border-light); color: var(--text-primary); } -.file-list .file-path { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 300px; display: block; } -.file-list .position { font-family: var(--font-mono); font-size: 12px; color: var(--text-secondary); } -.explanation-panel { flex: 1; padding-left: 16px; min-width: 0; overflow-wrap: break-word; word-wrap: break-word; } -.explanation-panel h4 { font-size: calc(13px * 0.875); text-transform: uppercase; font-weight: 600; color: var(--text-secondary); padding: 6px 0; border-bottom: 1px solid var(--border-color); height: 32px; margin: 0; } -.explanation-panel p { font-size: 13px; padding: 8px 0; color: var(--text-primary); line-height: 1.6; white-space: normal; word-break: break-word; } - -/* ── Experimental badge ──────────────────────────────────── */ -.badge-experimental { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; background: #fef9c3; color: #854d0e; vertical-align: middle; margin-left: 8px; cursor: default; } - -/* ── Footer ──────────────────────────────────────────────── */ -.footer { text-align: center; margin-top: 24px; color: var(--text-muted); font-size: 13px; } - -/* ── Responsive ──────────────────────────────────────────── */ -@media (max-width: 800px) { - .main { padding: 16px; } - .app-info-container { flex-direction: column; } - .domain-summary-container { flex-direction: column; } - .domain-summary-item { border-right: none; border-bottom: 1px solid var(--border-light); padding-bottom: 12px; } - .detail-content { flex-direction: column; } - .file-list { flex: none; width: 100%; } - .explanation-panel { padding-left: 0; padding-top: 8px; } -} -/* ── Tab navigation ──────────────────────────────────────── */ -.tab-nav { display: flex; gap: 0; border-bottom: 1px solid var(--border-color); margin-bottom: 28px; flex-wrap: wrap; } -.tab-btn { background: none; border: none; border-bottom: 2px solid transparent; padding: 10px 18px; font-size: 13px; font-family: var(--font-family); color: var(--text-secondary); cursor: pointer; margin-bottom: -1px; white-space: nowrap; position: relative; } -.tab-btn:hover { color: var(--text-primary); } -.tab-btn.active { color: var(--link-color); border-bottom-color: var(--link-color); font-weight: 600; } -.tab-panel { display: none; } -.tab-panel.active { display: block; } - -/* ── Tooltip on tab ──────────────────────────────────────── */ -.tab-btn[data-tooltip]::after { content: attr(data-tooltip); position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%); background: #24292f; color: #ffffff; font-size: 12px; font-weight: 400; padding: 6px 10px; border-radius: 4px; white-space: normal; width: max-content; max-width: 260px; text-align: center; pointer-events: none; opacity: 0; transition: opacity 0.15s; z-index: 100; } -.tab-btn[data-tooltip]:hover::after { opacity: 1; } - -/* ── Fact content — prose column ─────────────────────────── */ -.fact-content { max-width: 860px; margin: 0 auto; line-height: 1.75; color: var(--text-primary); font-size: 14px; } -.fact-content h1 { font-size: 22px; font-weight: 700; margin: 0 0 6px 0; color: var(--text-primary); letter-spacing: -0.01em; } -.fact-content h1 + p { margin-top: 6px; color: var(--text-secondary); font-size: 14px; margin-bottom: 24px; } -.fact-content h2 { font-size: 17px; font-weight: 700; margin: 36px 0 12px 0; padding-bottom: 6px; border-bottom: 1px solid var(--border-color); color: var(--text-primary); letter-spacing: -0.01em; } -.fact-content h3 { font-size: 14px; font-weight: 700; margin: 24px 0 8px 0; color: var(--text-primary); text-transform: uppercase; letter-spacing: 0.04em; font-size: 12px; color: var(--text-secondary); } -.fact-content p { margin: 10px 0; color: var(--text-primary); } -.fact-content ul, .fact-content ol { margin: 10px 0 10px 22px; } -.fact-content li { margin: 5px 0; } -.fact-content a { color: var(--link-color); } -.fact-content a:hover { text-decoration: underline; } - -/* ── Fact tables ─────────────────────────────────────────── */ -.table-wrap { overflow-x: auto; margin: 16px 0; } -.fact-content table { border-collapse: collapse; width: 100%; margin: 16px 0; font-size: 13px; border-radius: 6px; overflow: hidden; border: 1px solid var(--border-color); } -.fact-content thead { background: var(--bg-card); } -.fact-content th { text-align: left; font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; padding: 8px 14px; border-bottom: 1px solid var(--border-color); color: var(--text-secondary); } -.fact-content td { padding: 9px 14px; border-bottom: 1px solid var(--border-light); vertical-align: top; color: var(--text-primary); } -.fact-content tr:last-child td { border-bottom: none; } -.fact-content tbody tr:hover { background: #f6f8fa; } - -/* ── Fact code ───────────────────────────────────────────── */ -.fact-content code { font-family: var(--font-mono); font-size: 12px; background: #f0f1f3; padding: 2px 6px; border-radius: 4px; color: #c7254e; } -.fact-content pre { background: #f6f8fa; border: 1px solid var(--border-light); border-radius: 6px; padding: 14px 16px; overflow-x: auto; margin: 14px 0; } -.fact-content pre code { background: none; color: var(--text-primary); padding: 0; border-radius: 0; } -.fact-content hr { border: none; border-top: 1px solid var(--border-light); margin: 28px 0; } -.fact-content strong { font-weight: 600; } -.fact-content em { font-style: italic; } -.fact-content blockquote { border-left: 3px solid var(--border-color); margin: 12px 0; padding: 4px 16px; color: var(--text-secondary); } -.fact-unavailable { padding: 32px 0; } -.fact-unavailable h3 { font-size: 16px; font-weight: 600; color: var(--text-primary); text-transform: none; letter-spacing: 0; margin: 0 0 12px 0; } -.fact-unavailable p { margin: 0 0 8px 0; color: var(--text-secondary); } -.fact-unavailable ol, .fact-unavailable ul { margin: 0 0 0 22px; color: var(--text-primary); } -.fact-unavailable li { margin: 6px 0; } - -/* ── Mermaid diagram card ─────────────────────────────────── */ -.mermaid { background: var(--bg-card); border: 1px solid var(--border-light); border-radius: 8px; padding: 24px 16px; margin: 20px 0; overflow-x: auto; text-align: center; cursor: pointer; position: relative; } -.mermaid:hover { border-color: var(--border-color); } -.mermaid svg { max-width: 100%; height: auto !important; display: inline-block; } -.mermaid-zoom-hint { position: absolute; top: 8px; right: 10px; font-size: 11px; color: var(--text-muted); opacity: 0; transition: opacity 0.15s; pointer-events: none; } -.mermaid:hover .mermaid-zoom-hint { opacity: 1; } - -/* ── Diagram lightbox ────────────────────────────────────── */ -.diagram-lightbox { display: none; position: fixed; inset: 0; z-index: 1000; background: rgba(0,0,0,0.7); backdrop-filter: blur(2px); align-items: center; justify-content: center; cursor: zoom-out; padding: 48px 32px; } -.diagram-lightbox.open { display: flex; } -.diagram-lightbox-inner { background: #ffffff; border-radius: 10px; padding: 32px; width: 88vw; max-height: 88vh; overflow: hidden; cursor: default; box-shadow: 0 24px 64px rgba(0,0,0,0.35); display: flex; align-items: center; justify-content: center; } -.diagram-lightbox-inner svg { width: 100% !important; height: auto !important; max-height: 76vh; display: block; } -.diagram-lightbox-close { position: fixed; top: 20px; right: 24px; background: #ffffff; border: none; border-radius: 50%; width: 36px; height: 36px; font-size: 18px; cursor: pointer; display: flex; align-items: center; justify-content: center; color: var(--text-primary); box-shadow: 0 2px 8px rgba(0,0,0,0.2); z-index: 1001; } -.diagram-lightbox-close:hover { background: var(--bg-card); } -.diagram-lightbox-hint { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.55); color: #ffffff; font-size: 12px; padding: 6px 14px; border-radius: 20px; pointer-events: none; white-space: nowrap; z-index: 1001; }""" - -# ── Mermaid head script ────────────────────────────────────────────────────── -MERMAID_HEAD_SCRIPT = """<script> -window.__mermaidPending = []; -window.__mermaidReady = false; -window.__renderMermaidIn = function(container) { - if (container) { window.__mermaidPending.push(container); } -}; -</script> -<script type="module"> -import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs'; -mermaid.initialize({ startOnLoad: false, theme: 'default', flowchart: { useMaxWidth: true }, sequence: { useMaxWidth: true } }); - -window.__renderMermaidIn = function(container) { - var nodes = container ? container.querySelectorAll('.mermaid:not([data-processed])') : []; - if (nodes.length > 0) { - mermaid.run({ nodes: Array.from(nodes) }).then(function() { - nodes.forEach(function(node) { - if (!node.querySelector('.mermaid-zoom-hint')) { - var hint = document.createElement('span'); - hint.className = 'mermaid-zoom-hint'; - hint.textContent = 'Click to expand'; - node.appendChild(hint); - } - }); - }); - } -}; - -var drained = new Set(); -(window.__mermaidPending || []).forEach(function(c) { - if (!drained.has(c)) { drained.add(c); window.__renderMermaidIn(c); } -}); -window.__mermaidPending = []; -window.__mermaidReady = true; - -function renderActivePanels() { - document.querySelectorAll('.tab-panel.active').forEach(function(p) { - if (!drained.has(p)) { drained.add(p); window.__renderMermaidIn(p); } - }); -} -if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', renderActivePanels); -} else { - renderActivePanels(); -} -</script>""" - -# ── JavaScript ─────────────────────────────────────────────────────────────── -MAIN_SCRIPT = r"""function toggleRow(rowId, triggerRow) { - var detail = document.getElementById(rowId); - var btn = document.getElementById('btn-' + rowId); - if (!detail) return; - var visible = detail.style.display !== 'none'; - detail.style.display = visible ? 'none' : 'table-row'; - if (btn) btn.classList.toggle('open', !visible); -} -(function() { - var nav = document.querySelector('.tab-nav'); - if (!nav) return; - nav.addEventListener('click', function(e) { - var btn = e.target.closest('.tab-btn'); - if (!btn) return; - var targetId = btn.getAttribute('data-tab'); - document.querySelectorAll('.tab-btn').forEach(function(b) { b.classList.remove('active'); }); - document.querySelectorAll('.tab-panel').forEach(function(p) { p.classList.remove('active'); }); - btn.classList.add('active'); - var panel = document.getElementById(targetId); - if (panel) { - panel.classList.add('active'); - if (window.__mermaidReady) { window.__renderMermaidIn(panel); } - } - }); -})(); - -// ── Diagram lightbox ──────────────────────────────────────── -(function() { - var lightbox = document.getElementById('diagram-lightbox'); - var inner = document.getElementById('diagram-lightbox-inner'); - var closeBtn = document.getElementById('diagram-lightbox-close'); - if (!lightbox || !inner || !closeBtn) return; - - var scale = 1; - var translateX = 0; - var translateY = 0; - var isDragging = false; - var dragStartX = 0; - var dragStartY = 0; - - function applyTransform() { - var svg = inner.querySelector('svg'); - if (svg) { - svg.style.transform = 'translate(' + translateX + 'px, ' + translateY + 'px) scale(' + scale + ')'; - svg.style.transformOrigin = 'center center'; - svg.style.transition = 'none'; - } - } - - function resetTransform() { - scale = 1; translateX = 0; translateY = 0; - applyTransform(); - } - - function openLightbox(svgEl) { - inner.innerHTML = svgEl.outerHTML; - var injected = inner.querySelector('svg'); - if (injected) { - injected.removeAttribute('width'); - injected.removeAttribute('height'); - injected.style.cursor = 'grab'; - } - resetTransform(); - lightbox.classList.add('open'); - document.body.style.overflow = 'hidden'; - } - - function closeLightbox() { - lightbox.classList.remove('open'); - inner.innerHTML = ''; - document.body.style.overflow = ''; - resetTransform(); - } - - inner.addEventListener('wheel', function(e) { - e.preventDefault(); - var delta = e.deltaY > 0 ? 0.9 : 1.1; - scale = Math.min(Math.max(scale * delta, 0.3), 8); - applyTransform(); - }, { passive: false }); - - inner.addEventListener('mousedown', function(e) { - if (e.button !== 0) return; - isDragging = true; - dragStartX = e.clientX - translateX; - dragStartY = e.clientY - translateY; - var svg = inner.querySelector('svg'); - if (svg) { svg.style.cursor = 'grabbing'; } - e.preventDefault(); - }); - - document.addEventListener('mousemove', function(e) { - if (!isDragging) return; - translateX = e.clientX - dragStartX; - translateY = e.clientY - dragStartY; - applyTransform(); - }); - - document.addEventListener('mouseup', function() { - if (!isDragging) return; - isDragging = false; - var svg = inner.querySelector('svg'); - if (svg) { svg.style.cursor = 'grab'; } - }); - - inner.addEventListener('dblclick', function() { resetTransform(); }); - - document.addEventListener('click', function(e) { - var card = e.target.closest('.mermaid'); - if (!card) return; - if (e.target.closest('.diagram-lightbox-close')) return; - var svg = card.querySelector('svg'); - if (svg) { openLightbox(svg); } - }); - - closeBtn.addEventListener('click', closeLightbox); - - lightbox.addEventListener('click', function(e) { - if (e.target === lightbox) { closeLightbox(); } - }); - - document.addEventListener('keydown', function(e) { - if (e.key === 'Escape') { closeLightbox(); } - }); -})(); - -var _currentTarget = ''; - -function toggleMultiSelect(id) { - var el = document.getElementById(id); - if (!el) return; - document.querySelectorAll('.multi-select.open').forEach(function(ms) { if (ms.id !== id) ms.classList.remove('open'); }); - el.classList.toggle('open'); -} - -document.addEventListener('click', function(e) { - if (!e.target.closest('.multi-select')) { - document.querySelectorAll('.multi-select.open').forEach(function(ms) { ms.classList.remove('open'); }); - } -}); - -function getMultiSelectValues(msId) { - var el = document.getElementById(msId); - if (!el) return []; - var checked = el.querySelectorAll('input[type=checkbox]:checked'); - var vals = []; - for (var i = 0; i < checked.length; i++) vals.push(checked[i].value); - return vals; -} - -function onMultiSelectChange(msId, label) { - var el = document.getElementById(msId); - if (!el) return; - var vals = getMultiSelectValues(msId); - var total = el.querySelectorAll('input[type=checkbox]').length; - var btn = el.querySelector('.multi-select-btn'); - if (btn) { - btn.textContent = vals.length > 0 && vals.length < total ? label + ' (' + vals.length + ')' : label; - } - applyFilters(); -} - -function applyFilters() { - var selectedDomains = getMultiSelectValues('domain-ms'); - var selectedCrits = getMultiSelectValues('criticality-ms'); - - var sections = document.querySelectorAll('.issue-section'); - for (var i = 0; i < sections.length; i++) { - var domain = sections[i].getAttribute('data-domain'); - sections[i].style.display = (selectedDomains.length === 0 || selectedDomains.indexOf(domain) >= 0) ? '' : 'none'; - } - - var rows = document.querySelectorAll('.issue-row'); - for (var i = 0; i < rows.length; i++) { - var row = rows[i]; - var section = row.closest('.issue-section'); - if (section && section.style.display === 'none') { - row.style.display = 'none'; - var next = row.nextElementSibling; - if (next && next.classList.contains('detail-row')) next.style.display = 'none'; - continue; - } - - var show = true; - if (_currentTarget && row.hasAttribute('data-targets')) { - var targetsStr = row.getAttribute('data-targets'); - if (targetsStr && targetsStr !== '{}') { - try { - var targets = JSON.parse(targetsStr); - if (Object.keys(targets).length > 0 && !targets[_currentTarget]) { - show = false; - } - } catch(e) {} - } - } - - if (show && selectedCrits.length > 0) { - var crit = row.getAttribute('data-criticality'); - if (selectedCrits.indexOf(crit) < 0) show = false; - } - - row.style.display = show ? '' : 'none'; - var nextRow = row.nextElementSibling; - if (nextRow && nextRow.classList.contains('detail-row')) { - if (!show) nextRow.style.display = 'none'; - } - } -} - -function clearAllFilters() { - document.querySelectorAll('.multi-select input[type=checkbox]').forEach(function(cb) { cb.checked = false; }); - var domainBtn = document.querySelector('#domain-ms .multi-select-btn'); - if (domainBtn) domainBtn.textContent = 'Domain'; - var critBtn = document.querySelector('#criticality-ms .multi-select-btn'); - if (critBtn) critBtn.textContent = 'Criticality'; - applyFilters(); -} - -function onTargetChange(targetId) { - _currentTarget = targetId; - var rows = document.querySelectorAll('.issue-row[data-targets]'); - var totalEffort = 0; - var domainCrits = {}; - - for (var i = 0; i < rows.length; i++) { - var row = rows[i]; - var targets = JSON.parse(row.getAttribute('data-targets')); - var section = row.closest('.issue-section'); - var domain = section ? section.getAttribute('data-domain') : ''; - - if (!targets || Object.keys(targets).length === 0) { - var sp = parseFloat(row.querySelector('.sp-cell').textContent) || 0; - totalEffort += sp; - if (!domainCrits[domain]) domainCrits[domain] = {mandatory:0,potential:0,optional:0}; - var crit = row.getAttribute('data-criticality') || 'optional'; - domainCrits[domain][crit] = (domainCrits[domain][crit] || 0) + 1; - continue; - } - - var override = targets[targetId]; - if (!override) { - continue; - } - - var newSeverity = (override.severity || row.getAttribute('data-default-severity') || '').toLowerCase(); - row.setAttribute('data-criticality', newSeverity); - var critCell = row.querySelector('.crit-cell'); - if (critCell) { - var critText = newSeverity === 'mandatory' ? 'Mandatory' : newSeverity === 'potential' ? 'Potential' : 'Optional'; - var cssClass = newSeverity === 'mandatory' ? 'crit-square-mandatory' : newSeverity === 'potential' ? 'crit-square-potential' : 'crit-square-optional'; - critCell.innerHTML = '<span class="crit-label"><span class="crit-square ' + cssClass + '"></span>' + critText + '</span>'; - } - - var newEffort = override.effort !== undefined ? override.effort : parseFloat(row.getAttribute('data-default-effort')) || 0; - var spCell = row.querySelector('.sp-cell'); - if (spCell) spCell.textContent = newEffort; - totalEffort += newEffort; - - if (!domainCrits[domain]) domainCrits[domain] = {mandatory:0,potential:0,optional:0}; - domainCrits[domain][newSeverity] = (domainCrits[domain][newSeverity] || 0) + 1; - } - - var secSection = document.querySelector('.issue-section[data-domain="security"]'); - if (secSection) { - var secRows = secSection.querySelectorAll('.issue-row'); - for (var j = 0; j < secRows.length; j++) { - if (!secRows[j].hasAttribute('data-targets') || secRows[j].getAttribute('data-targets') === '') { - var sp = parseFloat(secRows[j].querySelector('.sp-cell').textContent) || 0; - totalEffort += sp; - if (!domainCrits['security']) domainCrits['security'] = {mandatory:0,potential:0,optional:0}; - var sc = secRows[j].getAttribute('data-criticality') || 'optional'; - domainCrits['security'][sc] = (domainCrits['security'][sc] || 0) + 1; - } - } - } - - var label = totalEffort < 20 ? 'S' : totalEffort < 50 ? 'M' : totalEffort < 100 ? 'L' : 'XL'; - var effortEl = document.getElementById('effort-display'); - if (effortEl) effortEl.textContent = label + ' (total story points: ' + totalEffort + ')'; - - updateDonuts(domainCrits); - applyFilters(); -} - -function updateDonuts(domainCrits) { - var container = document.getElementById('donut-container'); - if (!container) return; - container.innerHTML = ''; - var domainNames = {'cloud-readiness':'Cloud Readiness','java-upgrade':'Java Upgrade','security':'Security'}; - var domainOrder = ['cloud-readiness','java-upgrade','security']; - for (var d = 0; d < domainOrder.length; d++) { - var key = domainOrder[d]; - var c = domainCrits[key]; - if (!c || (c.mandatory + c.potential + c.optional) === 0) continue; - var total = c.mandatory + c.potential + c.optional; - var div = document.createElement('div'); - div.className = 'domain-summary-item'; - div.innerHTML = buildDonutSvg(c.mandatory, c.potential, c.optional) + - '<h3>' + domainNames[key] + '</h3>' + - '<div class="count">' + total + ' issue' + (total !== 1 ? 's' : '') + '</div>'; - container.appendChild(div); - } -} - -function buildDonutSvg(m, p, o) { - var total = m + p + o; - if (total === 0) return '<svg viewBox="0 0 100 100" width="100" height="100" style="display:block;margin:0 auto;"><circle cx="50" cy="50" r="35" fill="none" stroke="#d0d7de" stroke-width="15" opacity="0.5"/></svg>'; - var r = 35, circ = 2 * Math.PI * r, offset = 0; - var svg = '<svg viewBox="0 0 100 100" width="100" height="100" style="display:block;margin:0 auto;transform:rotate(-90deg);">'; - var segs = [{c:m,color:'var(--color-mandatory)'},{c:p,color:'var(--color-potential)'},{c:o,color:'var(--color-optional)'}]; - for (var i = 0; i < segs.length; i++) { - if (segs[i].c === 0) continue; - var segLen = (segs[i].c / total) * circ; - var gap = total > segs[i].c ? 1.5 : 0; - var drawLen = Math.max(segLen - gap, 0.5); - svg += '<circle cx="50" cy="50" r="' + r + '" fill="none" stroke="' + segs[i].color + '" stroke-width="15" stroke-dasharray="' + drawLen.toFixed(2) + ' ' + circ.toFixed(2) + '" stroke-dashoffset="' + (-offset).toFixed(2) + '"/>'; - offset += segLen; - } - svg += '</svg>'; - return svg; -}""" - - -# ── Helpers ────────────────────────────────────────────────────────────────── - -def get_severity_rank(severity): - if not severity: - return 99 - s = severity.strip().lower() - return {"mandatory": 0, "potential": 1, "optional": 2}.get(s, 99) - - -def build_donut_svg(mandatory, potential, optional): - total = mandatory + potential + optional - if total == 0: - return ('<svg viewBox="0 0 100 100" width="100" height="100" style="display:block;margin:0 auto;">' - '<circle cx="50" cy="50" r="35" fill="none" stroke="#d0d7de" stroke-width="15" opacity="0.5"/>' - '</svg>\n') - r = 35 - circ = 2 * math.pi * r - segments = [ - (mandatory, "var(--color-mandatory)"), - (potential, "var(--color-potential)"), - (optional, "var(--color-optional)"), - ] - svg = '<svg viewBox="0 0 100 100" width="100" height="100" style="display:block;margin:0 auto;transform:rotate(-90deg);">\n' - offset = 0.0 - for count, color in segments: - if count == 0: - continue - seg_len = (count / total) * circ - gap = 1.5 if total > count else 0 - draw_len = max(seg_len - gap, 0.5) - svg += (f' <circle cx="50" cy="50" r="{r}" fill="none" stroke="{color}" ' - f'stroke-width="15" stroke-dasharray="{draw_len:.2f} {circ:.2f}" ' - f'stroke-dashoffset="{-offset:.2f}"/>\n') - offset += seg_len - svg += '</svg>\n' - return svg - - -def simple_markdown_to_html(md): - """Convert rule description markdown to inline HTML.""" - if not md: - return "" - html = escape(md) - # Code blocks - html = re.sub(r'```\w*\r?\n([\s\S]*?)```', r'<pre><code>\1</code></pre>', html) - # Inline code - html = re.sub(r'`([^`]+)`', r'<code>\1</code>', html) - # Bold - html = re.sub(r'\*\*(.+?)\*\*', r'<strong>\1</strong>', html) - # Links - def replace_link(m): - text, url = m.group(1), m.group(2) - return f'<a href="{url}" target="_blank" rel="noopener noreferrer">{text}</a>' - html = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', replace_link, html) - - def apply_italic(text): - return re.sub(r'\*(.+?)\*', r'<em>\1</em>', text) - - # Process line-by-line for block structures - lines = re.split(r'\r?\n', html) - result = [] - in_list = False - in_paragraph = False - - for line in lines: - # List item - list_match = re.match(r'^[\-\*]\s+(.+)', line) - if list_match: - if in_paragraph: - result.append('</p>') - in_paragraph = False - if not in_list: - result.append('<ul>') - in_list = True - content = apply_italic(list_match.group(1)) - result.append(f'<li>{content}</li>') - continue - - # End list if needed - if in_list: - result.append('</ul>') - in_list = False - - # Blank line - if not line.strip(): - if in_paragraph: - result.append('</p>') - in_paragraph = False - continue - - # Regular text - text_content = apply_italic(line) - if not in_paragraph: - result.append('<p>') - in_paragraph = True - else: - result.append('<br/>') - result.append(text_content) - - if in_list: - result.append('</ul>') - if in_paragraph: - result.append('</p>') - - return ''.join(result) - - -def markdown_to_fact_html(md): - """Convert a fact .md file to HTML for the fact-content div.""" - if not md: - return "" - lines = md.split('\n') - out = [] - i = 0 - in_list = None # 'ul' or 'ol' - in_table = False - table_lines = [] - - def flush_table(): - nonlocal in_table, table_lines - if not table_lines: - return - # Parse markdown table - header = table_lines[0] - # table_lines[1] is the separator - rows = table_lines[2:] if len(table_lines) > 2 else [] - cols = [c.strip() for c in header.strip('|').split('|')] - out.append('<div class="table-wrap"><table><thead><tr>') - out.append(''.join(f'<th>{inline_md(c)}</th>' for c in cols)) - out.append('</tr></thead><tbody>') - for row in rows: - cells = [c.strip() for c in row.strip('|').split('|')] - out.append('<tr>' + ''.join(f'<td>{inline_md(c)}</td>' for c in cells) + '</tr>') - out.append('</tbody></table></div>') - table_lines = [] - in_table = False - - def flush_list(): - nonlocal in_list - if in_list: - out.append(f'</{in_list}>') - in_list = None - - def inline_md(text): - """Convert inline markdown.""" - t = text - t = re.sub(r'`([^`]+)`', r'<code>\1</code>', t) - t = re.sub(r'\*\*(.+?)\*\*', r'<strong>\1</strong>', t) - t = re.sub(r'\*(.+?)\*', r'<em>\1</em>', t) - t = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'<a href="\2">\1</a>', t) - return t - - while i < len(lines): - line = lines[i] - - # Mermaid code block - if line.strip().startswith('```mermaid'): - flush_list() - flush_table() - i += 1 - mermaid_lines = [] - while i < len(lines) and not lines[i].strip().startswith('```'): - mermaid_lines.append(lines[i]) - i += 1 - i += 1 # skip closing ``` - out.append('<div class="mermaid">') - out.append('\n'.join(mermaid_lines)) - out.append('</div>') - continue - - # Regular code block - if line.strip().startswith('```'): - flush_list() - flush_table() - i += 1 - code_lines = [] - while i < len(lines) and not lines[i].strip().startswith('```'): - code_lines.append(escape(lines[i])) - i += 1 - i += 1 - out.append('<pre><code>') - out.append('\n'.join(code_lines)) - out.append('</code></pre>') - continue - - # Table detection - if '|' in line and i + 1 < len(lines) and re.match(r'^\s*\|?[\s\-:|]+\|', lines[i + 1]): - flush_list() - if not in_table: - in_table = True - table_lines = [] - table_lines.append(line) - i += 1 - continue - if in_table and '|' in line: - table_lines.append(line) - i += 1 - continue - if in_table: - flush_table() - - # Headers - m = re.match(r'^(#{1,3})\s+(.+)', line) - if m: - flush_list() - level = len(m.group(1)) - text = m.group(2).strip() - tag = f'h{level}' - hid = re.sub(r'[^a-z0-9]+', '-', text.lower()).strip('-') - out.append(f'<{tag} id="{hid}">{inline_md(text)}</{tag}>') - i += 1 - continue - - # Unordered list - m = re.match(r'^[\-\*]\s+(.+)', line) - if m: - flush_table() - if in_list != 'ul': - flush_list() - in_list = 'ul' - out.append('<ul>') - out.append(f'<li>{inline_md(m.group(1))}</li>') - i += 1 - continue - - # Ordered list - m = re.match(r'^\d+\.\s+(.+)', line) - if m: - flush_table() - if in_list != 'ol': - flush_list() - in_list = 'ol' - out.append('<ol>') - out.append(f'<li>{inline_md(m.group(1))}</li>') - i += 1 - continue - - # Blank line - if not line.strip(): - flush_list() - i += 1 - continue - - # Paragraph - flush_list() - out.append(f'<p>{inline_md(line)}</p>') - i += 1 - - flush_list() - flush_table() - return '\n'.join(out) - - -def is_dotnet_component(language): - if not language or language.strip().upper() == "N/A": - return True - return ".net" in language.lower() or "c#" in language.lower() - - -# ── Fact tabs catalog ──────────────────────────────────────────────────────── -FACT_TABS = [ - ("tab-arch", "Architecture", "How the app's layers connect — from UI to data.", "architecture-diagram.md"), - ("tab-api", "API Contracts", "Endpoints exposed by this app and how services communicate.", "api-service-contracts.md"), - ("tab-config", "Configuration", "Environment settings, runtime profiles, and deployment manifests.", "configuration-inventory.md"), - ("tab-workflows", "Business Workflows", "Domain entities and the core flows users move through.", "business-workflows.md"), - ("tab-deps", "Dependencies", "Libraries and frameworks this app depends on, grouped by category.", "dependency-map.md"), - ("tab-data", "Data Model", "Database schema, entity relationships, and persistence behavior.", "data-architecture.md"), -] - - -# ── Main generation logic ──────────────────────────────────────────────────── - -def generate_from_report_json(report_dir): - """Generate report.html from report.json (Java/.NET).""" - report_json_path = os.path.join(report_dir, "report.json") - if not os.path.isfile(report_json_path): - print(f"Error: report.json not found at {report_json_path}", file=sys.stderr) - sys.exit(1) - - with open(report_json_path, 'r', encoding='utf-8') as f: - root = json.load(f) - - # Parse metadata - metadata = root.get("metadata", {}) - target_display_names = metadata.get("targetDisplayNames", []) - target_ids = metadata.get("targetIds", []) - - # Parse rules - rules = root.get("rules", {}) - - # Parse projects/incidents - projects = root.get("projects", []) - # Merge all projects into one component view - component_name = "" - component_display_name = "" - language = "N/A" - frameworks = "N/A" - build_tools = "N/A" - jdk_version = "" - all_incidents = [] - - for proj in projects: - props = proj.get("properties", {}) - if not component_name: - component_name = props.get("repo", "") or props.get("appName", "Application") - component_display_name = props.get("appName", "") or component_name - lang = props.get("languages") - if lang: - language = ", ".join(lang) if isinstance(lang, list) else str(lang) - fw = props.get("frameworks") - if fw: - frameworks = ", ".join(fw) if isinstance(fw, list) else str(fw) - tools = props.get("tools") - if tools: - build_tools = ", ".join(tools) if isinstance(tools, list) else str(tools) - if not jdk_version: - jdk_version = props.get("jdkVersion", "") - - for incident in proj.get("incidents", []): - rule_id = incident.get("ruleId", "") - if rule_id and rule_id in rules: - all_incidents.append(incident) - - # Parse security findings - security_findings = root.get("security", []) - # Parse rearchitect findings - rearchitect_findings = root.get("rearchitect", []) - - is_dotnet = is_dotnet_component(language) - - # Group incidents by rule - incidents_by_rule = {} - for inc in all_incidents: - rid = inc.get("ruleId", "") - if rid not in incidents_by_rule: - incidents_by_rule[rid] = [] - incidents_by_rule[rid].append(inc) - - # Classify into cloud/upgrade domains - cloud_groups = [] # list of (rule_id, incidents_list) - upgrade_groups = [] - upgrade_domain_name = "Upgrade" - - for rule_id, incs in incidents_by_rule.items(): - rule_obj = rules.get(rule_id, {}) - labels = rule_obj.get("labels", []) - has_domain = any(l.startswith("domain=") for l in labels) - is_cloud = any(l == "domain=cloud-readiness" for l in labels) - is_upgrade = any(l.endswith("-upgrade") for l in labels) - - if not has_domain and is_dotnet: - is_cloud = True - - if is_cloud: - cloud_groups.append((rule_id, incs)) - if is_upgrade: - upgrade_groups.append((rule_id, incs)) - upgrade_label = next((l for l in labels if l.endswith("-upgrade") and l.startswith("domain=")), None) - if upgrade_label: - domain_val = upgrade_label[len("domain="):] - upgrade_domain_name = " ".join(w.capitalize() for w in domain_val.split("-")) - - # Count criticalities per domain - def count_crits(groups): - m, p, o = 0, 0, 0 - for rid, _ in groups: - sev = rules.get(rid, {}).get("severity", "").strip().lower() - if sev == "mandatory": m += 1 - elif sev == "potential": p += 1 - else: o += 1 - return m, p, o - - cloud_crit = count_crits(cloud_groups) - upgrade_crit = count_crits(upgrade_groups) - - # Add rearchitect to upgrade optional count - if rearchitect_findings: - upgrade_crit = (upgrade_crit[0], upgrade_crit[1], upgrade_crit[2] + len(rearchitect_findings)) - - sec_mandatory = sum(1 for f in security_findings if f.get("severity", "").strip().lower() == "mandatory") - sec_potential = sum(1 for f in security_findings if f.get("severity", "").strip().lower() == "potential") - sec_optional = len(security_findings) - sec_mandatory - sec_potential - security_crit = (sec_mandatory, sec_potential, sec_optional) - - # Compute total effort - # For each unique rule, take aksEffort from first incident - total_effort = 0.0 - seen_rules = set() - for inc in all_incidents: - rid = inc.get("ruleId", "") - if rid in seen_rules: - continue - seen_rules.add(rid) - # Get AKS effort - aks_effort = 0 - targets = inc.get("targets", {}) - for tid, tdata in targets.items(): - if "aks" in tid.lower() or "kubernetes" in tid.lower(): - aks_effort = tdata.get("effort", 0) - break - if aks_effort == 0: - aks_effort = rules.get(rid, {}).get("effort", 0) or 0 - total_effort += aks_effort - - total_effort += sum(f.get("storyPoint", 0) for f in security_findings) - total_effort += len(rearchitect_findings) * 10 - - if total_effort < 20: - effort_label = "S" - elif total_effort < 50: - effort_label = "M" - elif total_effort < 100: - effort_label = "L" - else: - effort_label = "XL" - effort_display = f"{effort_label} (total story points: {int(total_effort)})" - - # Load fact tabs - facts_dir = os.path.join(report_dir, "facts") - # Fallback: if facts/ doesn't have the files, try engines/ directory - engines_dir = os.path.join(os.path.dirname(os.path.dirname(report_dir)), "engines") - fact_tab_data = [] # (tab_id, label, tooltip, html_content) - for tab_id, label, tooltip, filename in FACT_TABS: - fpath = os.path.join(facts_dir, filename) - if not os.path.isfile(fpath): - # Try engines/ as fallback - fpath = os.path.join(engines_dir, filename) - if os.path.isfile(fpath): - with open(fpath, 'r', encoding='utf-8') as f: - md_content = f.read() - html_content = markdown_to_fact_html(md_content) - fact_tab_data.append((tab_id, label, tooltip, html_content)) - - # ── Build HTML ─────────────────────────────────────────────────────────── - html = [] - html.append('<!DOCTYPE html>') - html.append('<html><head><meta charset="utf-8" />') - html.append('<meta name="viewport" content="width=device-width, initial-scale=1" />') - html.append(f'<title>Assessment - {escape(component_display_name or component_name)}') - html.append('') - html.append(MERMAID_HEAD_SCRIPT) - html.append('') - - # Lightbox - html.append('') - - html.append('
') - html.append(f'

{escape(component_display_name or component_name)}

') - - # App info card - html.append('
') - html.append('

Application Information

') - html.append('
') - html.append('
') - html.append('
') - html.append(f'
Application Name{escape(component_display_name or component_name)}
') - if jdk_version: - html.append(f'
Java Version{escape(jdk_version)}
') - html.append(f'
Effort{escape(effort_display)}
') - html.append('
') - html.append('
') - html.append(f'
Build Tools{escape(build_tools)}
') - html.append(f'
Frameworks{escape(frameworks)}
') - html.append('
') - html.append('
') - html.append('
') - html.append('
') - - # Tab navigation - html.append('') - - # Issues tab panel - html.append('
') - - # Target dropdown - if target_display_names and target_ids: - html.append('
') - html.append(' ') - html.append(' ') - html.append('
') - - # Issue Summary card - html.append('
') - html.append('

Issue Summary

') - html.append('
') - html.append('
') - - def render_domain_donut(title, m, p, o): - total = m + p + o - html.append('
') - html.append(build_donut_svg(m, p, o)) - html.append(f'

{escape(title)}

') - html.append(f'
{total} issue{"s" if total != 1 else ""}
') - html.append('
') - - if cloud_groups: - render_domain_donut("Cloud Readiness", *cloud_crit) - if upgrade_groups or rearchitect_findings: - render_domain_donut(upgrade_domain_name, *upgrade_crit) - if security_findings: - render_domain_donut("Security", *security_crit) - - html.append('
') - html.append('
') - html.append('
Mandatory
') - html.append('
Potential
') - html.append('
Optional
') - html.append('
') - html.append('
') - html.append('
') - - # Filter bar - available_domains = [] - if cloud_groups: - available_domains.append(("cloud-readiness", "Cloud Readiness")) - if upgrade_groups: - available_domains.append(("java-upgrade", upgrade_domain_name)) - if security_findings: - available_domains.append(("security", "Security")) - - available_crits = set() - for rid, _ in cloud_groups + upgrade_groups: - sev = rules.get(rid, {}).get("severity", "optional").strip().lower() - available_crits.add(sev) - for f in security_findings: - available_crits.add(f.get("severity", "optional").strip().lower() or "optional") - - html.append('
') - if len(available_domains) > 1: - html.append('
') - html.append(' ') - html.append('
') - for did, dlabel in available_domains: - html.append(f' ') - html.append('
') - html.append('
') - if len(available_crits) > 1: - html.append('
') - html.append(' ') - html.append('
') - for c in ["mandatory", "potential", "optional"]: - if c in available_crits: - html.append(f' ') - html.append('
') - html.append('
') - html.append(' Clear all') - html.append('
') - - # Issue sections - def render_issue_section(section_title, groups, domain_id, badge_html=None, extra_rearchitect=None): - if not groups and not extra_rearchitect: - return - html.append(f'
') - badge = badge_html or "" - html.append(f'

{escape(section_title)}{badge}

') - html.append(' ') - html.append(' ') - html.append(' ') - html.append(' ') - - # Sort by severity rank then count desc - ordered = sorted(groups, key=lambda g: (get_severity_rank(rules.get(g[0], {}).get("severity", "")), -len(g[1]))) - - section_no_spaces = section_title.replace(" ", "").lower() - for gi, (rule_id, incs) in enumerate(ordered): - rule_obj = rules.get(rule_id, {}) - title = rule_obj.get("title", rule_id) - severity = rule_obj.get("severity", "").strip().lower() - first_inc = incs[0] - - # Get AKS effort from first incident - story_point = 0 - targets_data = first_inc.get("targets", {}) - for tid, tdata in targets_data.items(): - if "aks" in tid.lower() or "kubernetes" in tid.lower(): - story_point = tdata.get("effort", 0) - break - if story_point == 0: - story_point = rule_obj.get("effort", 0) or 0 - - row_id = f"row-{section_no_spaces}-{gi}" - - if severity == "mandatory": - crit_text, crit_css = "Mandatory", "crit-square-mandatory" - elif severity == "potential": - crit_text, crit_css = "Potential", "crit-square-potential" - else: - crit_text, crit_css = "Optional", "crit-square-optional" - - # Build targets JSON - targets_json_obj = {} - for tid, tdata in targets_data.items(): - targets_json_obj[tid] = {"effort": tdata.get("effort", 0), "severity": tdata.get("severity")} - targets_json_str = json.dumps(targets_json_obj, separators=(',', ':')) - - html.append(f" ") - html.append(f' ') - html.append(f' ') - html.append(f' ') - html.append(' ') - - # Detail row - incidents_with_loc = [inc for inc in incs if inc.get("location")] - html.append(f' ') - html.append(' ') - html.append(' ') - - # Rearchitect findings - if extra_rearchitect: - for ri, finding in enumerate(extra_rearchitect): - row_id = f"row-{section_no_spaces}-rearch-{ri}" - old_val = finding.get("old", "") - explanation = finding.get("explanation", "") - detected_in = finding.get("detectedIn", {}) - all_files = (detected_in.get("configFiles") or []) + (detected_in.get("sourceFiles") or []) - - html.append(f' ') - html.append(f' ') - html.append(f' ') - html.append(f' ') - html.append(' ') - html.append(f' ') - html.append(' ') - html.append(' ') - - html.append(' ') - html.append('
Issue CategoryCriticalityStory Point
{escape(title)}
{crit_text}{story_point}
Framework obsoletion ({escape(old_val)})
Optional10
') - html.append('
') - - render_issue_section("Cloud Readiness", cloud_groups, "cloud-readiness") - render_issue_section(upgrade_domain_name, upgrade_groups, "java-upgrade", extra_rearchitect=rearchitect_findings) - - # Security section - if security_findings: - html.append('
') - html.append('

Security Experimental

') - html.append(' ') - html.append(' ') - html.append(' ') - html.append(' ') - - ordered_sec = sorted(security_findings, key=lambda f: (get_severity_rank(f.get("severity", "")), -f.get("storyPoint", 0))) - for fi, finding in enumerate(ordered_sec): - row_id = f"row-security-{fi}" - sev = finding.get("severity", "").strip().lower() - if sev == "mandatory": - crit_text, crit_css = "Mandatory", "crit-square-mandatory" - elif sev == "potential": - crit_text, crit_css = "Potential", "crit-square-potential" - else: - crit_text, crit_css = "Optional", "crit-square-optional" - - fid = finding.get("id", "") - ftitle = finding.get("title", "") - display_name = f"{fid}: {ftitle}" if ftitle else fid - sp = finding.get("storyPoint", 0) - - html.append(f' ') - html.append(f' ') - html.append(f' ') - html.append(f' ') - html.append(' ') - - html.append(f' ') - html.append(' ') - html.append(' ') - - html.append(' ') - html.append('
Issue CategoryCriticalityStory Point
{escape(display_name)}
{crit_text}{sp}
') - html.append('
') - - html.append('
') - - # Fact tab panels - for tab_id, _, _, content in fact_tab_data: - html.append(f'
') - html.append(f'
{content}
') - html.append('
') - - # Footer - html.append('') - - # Script - html.append('') - - # Initial target change - if target_ids: - html.append(f"") - - html.append('
') - - return '\n'.join(html) - - -def generate_from_js_markdown(report_dir): - """Generate report.html from js-assessment-report.md (JS/TS).""" - # Look for js-assessment-report.md in report_dir or parent directories - md_path = None - search_dir = report_dir - for _ in range(5): - candidate = os.path.join(search_dir, "js-assessment-report.md") - if os.path.isfile(candidate): - md_path = candidate - break - search_dir = os.path.dirname(search_dir) - - if not md_path: - print("Error: js-assessment-report.md not found", file=sys.stderr) - sys.exit(1) - - with open(md_path, 'r', encoding='utf-8') as f: - md_content = f.read() - - # Extract title from first heading - title_match = re.match(r'^#\s+(.+)', md_content) - title = title_match.group(1) if title_match else "Assessment Report" - - fact_html = markdown_to_fact_html(md_content) - - html = [] - html.append('') - html.append('') - html.append('') - html.append(f'Assessment - {escape(title)}') - html.append('') - html.append(MERMAID_HEAD_SCRIPT) - html.append('') - html.append('') - html.append('
') - html.append(f'

{escape(title)}

') - html.append(f'
{fact_html}
') - html.append('') - html.append('') - html.append('
') - - return '\n'.join(html) - - -def main(): - if len(sys.argv) < 2: - print("Usage: python generate_report_html.py /path/to/report-directory", file=sys.stderr) - sys.exit(1) - - report_dir = sys.argv[1] - if not os.path.isdir(report_dir): - print(f"Error: Directory not found: {report_dir}", file=sys.stderr) - sys.exit(1) - - report_json_path = os.path.join(report_dir, "report.json") - - if os.path.isfile(report_json_path): - output = generate_from_report_json(report_dir) - else: - # Look for JS/TS markdown - output = generate_from_js_markdown(report_dir) - - output_path = os.path.join(report_dir, "report.html") - with open(output_path, 'w', encoding='utf-8') as f: - f.write(output) - - print(f"Generated: {output_path}") - - -if __name__ == "__main__": - main() diff --git a/.github/skills/generate-report-md/SKILL.md b/.github/skills/generate-report-md/SKILL.md deleted file mode 100644 index a761d3951..000000000 --- a/.github/skills/generate-report-md/SKILL.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -name: generate-report-md -description: Generate report.md from assessment report data by running the generate-report-md.py script ---- - -# Generate Report Markdown - -Generate a `report.md` file from the assessment output by running the included Python script. - -## Execution - -Run the following command: - -```bash -python .github/modernize/assessment/engines/generate-report-md.py .github/modernize/assessment/reports/report-{reportId} -``` - -If the script is not at that path, look for it in the skill's own directory or copy it from there. The script file is `generate-report-md.py` bundled alongside this skill. - -Alternatively, if the script is available in the current working directory or skill resources: - -```bash -python generate-report-md.py .github/modernize/assessment/reports/report-{reportId} -``` - -## What the script does - -1. Reads `report.json` (Java/.NET) or `js-assessment-report.md` (JS/TS) from the report directory -2. Checks for available fact files in the `facts/` subdirectory -3. Generates `report.md` in the same directory - -## Prerequisites - -- Python 3 must be available in the environment -- The versioned report directory must already exist with `report.json` or the JS/TS report file -- Fact files should already be copied to the `facts/` subdirectory (if architecture analysis was enabled) - -## Input - -The script takes one argument: the path to the versioned report directory, e.g.: -`.github/modernize/assessment/reports/report-20240615143052` - -## Output - -`report.md` is written to the same directory as the input report. - -## Fallback - -If Python is not available or the script fails, you may generate report.md manually following the same structure as the local CLI renderer produces. The key requirements are: -- Summary table with Total Issues (count of unique rule groups, NOT file occurrences) -- Application Information table (without Story points row) -- Issue sections with anchor IDs derived from titles (not rule IDs) -- Codebase Insights section linking to available fact files -- No "Back to aggregate report" link diff --git a/.github/skills/generate-report-md/generate-report-md.py b/.github/skills/generate-report-md/generate-report-md.py deleted file mode 100644 index 2c01c3ed0..000000000 --- a/.github/skills/generate-report-md/generate-report-md.py +++ /dev/null @@ -1,579 +0,0 @@ -#!/usr/bin/env python3 -""" -Generates report.md from report.json (Java/.NET) or js-assessment-report.md (JS/TS). - -Usage: - python generate_report_md.py /path/to/.github/modernize/assessment/reports/report-{id} -""" - -import json -import os -import re -import sys - - -def make_anchor_id(title: str) -> str: - """Convert title to anchor ID: replace non-alnum/non-dash with _, collapse, trim trailing.""" - if not title: - return "issue" - result = [] - last_was_underscore = False - for ch in title: - if ch.isalnum() or ch == '-': - result.append(ch) - last_was_underscore = False - else: - if not last_was_underscore and result: - result.append('_') - last_was_underscore = True - if result and result[-1] == '_': - result.pop() - return ''.join(result) - - -def escape_table_cell(value: str) -> str: - if not value: - return "" - return value.replace("|", "\\|").replace("\r", "").replace("\n", " ") - - -def escape_html(value: str) -> str: - if not value: - return "" - return value.replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """) - - -def get_array_as_string(node) -> str: - """Convert a JSON node (array or string) to comma-separated string.""" - if node is None: - return "" - if isinstance(node, list): - values = [v for v in node if v and str(v).strip()] - return ", ".join(str(v) for v in values) if values else "" - if isinstance(node, str): - return node if node.strip() else "" - return str(node) - - -def get_severity_rank(severity: str) -> int: - s = (severity or "").strip().lower() - return {"mandatory": 0, "potential": 1, "optional": 2, "information": 3}.get(s, 999) - - -def is_dotnet_component(language: str) -> bool: - if not language or language.strip().lower() == "n/a": - return True - lang_lower = language.lower() - return ".net" in lang_lower or "c#" in lang_lower - - -def render_appcat_report(report_dir: str, report_json_path: str) -> str: - """Render report.md from report.json for Java/.NET.""" - with open(report_json_path, 'r', encoding='utf-8') as f: - root = json.load(f) - - rules = root.get("rules", {}) - projects = root.get("projects", []) - security_findings = root.get("security", []) - rearchitect_findings = root.get("rearchitect", []) - - if not projects: - return "" - - # Parse first component - project = projects[0] - properties = project.get("properties", {}) or {} - - component_name = properties.get("repo") or properties.get("appName") or "Others" - language = get_array_as_string(properties.get("languages")) or "N/A" - frameworks = get_array_as_string(properties.get("frameworks")) or "N/A" - build_tools = get_array_as_string(properties.get("tools")) or "N/A" - - # Merge all projects for same component - all_incidents = [] - for proj in projects: - incidents_array = proj.get("incidents", []) - for inc in incidents_array: - all_incidents.append(inc) - - # Group incidents by ruleId, track unique rules - rule_groups = {} # ruleId -> list of incidents - for inc in all_incidents: - rule_id = inc.get("ruleId", "") - if not rule_id or rule_id not in rules: - continue - rule_groups.setdefault(rule_id, []).append(inc) - - # Compute severity counts (unique rules) - mandatory_rules = set() - potential_rules = set() - optional_rules = set() - for rule_id in rule_groups: - rule_obj = rules.get(rule_id, {}) - severity = (rule_obj.get("severity") or "").strip().lower() - if severity == "mandatory": - mandatory_rules.add(rule_id) - elif severity == "potential": - potential_rules.add(rule_id) - elif severity == "optional": - optional_rules.add(rule_id) - - # Security finding counts - sec_mandatory = sum(1 for f in security_findings if (f.get("severity") or "").strip().lower() == "mandatory") - sec_potential = sum(1 for f in security_findings if (f.get("severity") or "").strip().lower() == "potential") - - total_issues = len(rule_groups) + len(security_findings) - mandatory_blockers = len(mandatory_rules) + sec_mandatory - potential_issues = len(potential_rules) + sec_potential - - # Classify rules into cloud/upgrade - is_dotnet = is_dotnet_component(language) - cloud_rules = [] - upgrade_rules = [] - - for rule_id, incidents in rule_groups.items(): - rule_obj = rules.get(rule_id, {}) - labels = rule_obj.get("labels", []) or [] - labels = [l for l in labels if l and str(l).strip()] - - has_domain_label = any(str(l).lower().startswith("domain=") for l in labels) - is_cloud = any(str(l).lower() == "domain=cloud-readiness" for l in labels) - is_upgrade = any(str(l).lower().endswith("-upgrade") for l in labels) - - if not has_domain_label and is_dotnet: - is_cloud = True - - if is_cloud: - cloud_rules.append(rule_id) - if is_upgrade: - upgrade_rules.append(rule_id) - - # Get AKS effort for each rule (first incident's target or rule-level) - def get_aks_effort(rule_id: str) -> float: - incidents = rule_groups.get(rule_id, []) - for inc in incidents: - targets = inc.get("targets", {}) or {} - for target_id, target_data in targets.items(): - if isinstance(target_data, dict) and "aks" in target_id.lower(): - effort = target_data.get("effort", 0) - if effort: - return float(effort) - break - # Fallback to rule-level - rule_obj = rules.get(rule_id, {}) - return float(rule_obj.get("effort", 0) or 0) - - def sort_key(rule_id): - rule_obj = rules.get(rule_id, {}) - severity = (rule_obj.get("severity") or "").strip().lower() - rank = get_severity_rank(severity) - count = len(rule_groups.get(rule_id, [])) - return (rank, -count) - - cloud_rules.sort(key=sort_key) - upgrade_rules.sort(key=sort_key) - - # Build markdown - lines = [] - lines.append(f"# {component_name}") - lines.append("") - lines.append("## Summary") - lines.append("") - lines.append("| Metric | Value |") - lines.append("|--------|-------|") - lines.append(f"| Total Issues | {total_issues} |") - lines.append(f"| Mandatory Blockers | {mandatory_blockers} |") - lines.append(f"| Potential Issues | {potential_issues} |") - lines.append("") - lines.append("## Application Information") - lines.append("") - lines.append("| Property | Value |") - lines.append("|----------|-------|") - lines.append(f"| Language | {escape_table_cell(language)} |") - lines.append(f"| Frameworks | {escape_table_cell(frameworks)} |") - lines.append(f"| Build tools | {escape_table_cell(build_tools)} |") - - # Conditional rows - jdk_version = properties.get("jdkVersion", "") - if jdk_version and str(jdk_version).strip(): - lines.append(f"| JDK version | {escape_table_cell(str(jdk_version))} |") - - description = properties.get("description", "") - if description and str(description).strip(): - lines.append(f"| Description | {escape_table_cell(str(description))} |") - - app_type = properties.get("applicationType", "") - if app_type and str(app_type).strip(): - lines.append(f"| Application type | {escape_table_cell(str(app_type))} |") - - tags = get_array_as_string(properties.get("tags")) - if tags: - lines.append(f"| Tags | {escape_table_cell(tags)} |") - - loc = properties.get("linesOfCode", "") - if loc and str(loc).strip(): - lines.append(f"| Lines of code | {escape_table_cell(str(loc))} |") - - # Do NOT include Story points - lines.append("") - - def render_issue_section(section_title: str, rule_ids: list): - if not rule_ids: - return - lines.append(f"## {section_title}") - lines.append("") - lines.append("| Issue Name | Criticality | Story Points | Occurrences |") - lines.append("|------------|-------------|--------------|-------------|") - - for rule_id in rule_ids: - rule_obj = rules.get(rule_id, {}) - title = rule_obj.get("title") or rule_id - severity = (rule_obj.get("severity") or "").strip().lower() - crit_label = {"mandatory": "Mandatory", "potential": "Potential"}.get(severity, "Optional") - effort = get_aks_effort(rule_id) - # Format effort: integer if whole, else float - effort_str = str(int(effort)) if effort == int(effort) else f"{effort:g}" - count = len(rule_groups.get(rule_id, [])) - anchor_id = make_anchor_id(title) - - # Check if any incident has a file path - has_locations = any( - (inc.get("location") or "").strip() - for inc in rule_groups.get(rule_id, []) - ) - occurrences_cell = f"[{count}](#{anchor_id})" if has_locations else str(count) - lines.append(f"| {escape_table_cell(title)} | {crit_label} | {effort_str} | {occurrences_cell} |") - - lines.append("") - lines.append("### Issue Details") - lines.append("") - - for rule_id in rule_ids: - rule_obj = rules.get(rule_id, {}) - title = rule_obj.get("title") or rule_id - incidents = rule_groups.get(rule_id, []) - - locations = [] - for inc in incidents: - file_path = (inc.get("location") or "").strip() - if not file_path: - continue - line_num = inc.get("line") - if line_num is not None: - try: - line_num = int(line_num) - locations.append(f"{file_path} (line {line_num})") - except (ValueError, TypeError): - locations.append(file_path) - else: - locations.append(file_path) - - if not locations: - continue - - anchor_id = make_anchor_id(title) - lines.append(f'
') - lines.append(f"{escape_html(title)} — affected files") - lines.append("") - for loc in locations: - lines.append(f"- `{loc}`") - lines.append("") - lines.append("
") - lines.append("") - - render_issue_section("Cloud Readiness Issues", cloud_rules) - render_issue_section("Upgrade Issues", upgrade_rules) - - # Rearchitect findings - if rearchitect_findings: - lines.append("## Rearchitect Findings") - lines.append("") - lines.append("> **Note:** These findings were generated by AI and may contain inaccuracies or incomplete information. Please review carefully.") - lines.append("") - lines.append("| Finding | Old | New | Story Points | Files |") - lines.append("|---------|-----|-----|--------------|-------|") - - for finding in rearchitect_findings: - name = finding.get("name", "") - old = finding.get("old", "") - new = finding.get("new", "") - story_points = finding.get("storyPoints", 5) - detected_in = finding.get("detectedIn", {}) or {} - config_files = detected_in.get("configFiles", []) or [] - source_files = detected_in.get("sourceFiles", []) or [] - all_files = config_files + source_files - anchor_id = make_anchor_id(name) - files_cell = f"[{len(all_files)}](#{anchor_id})" if all_files else "0" - lines.append(f"| {escape_table_cell(name)} | {escape_table_cell(old)} | {escape_table_cell(new)} | {story_points} | {files_cell} |") - - lines.append("") - lines.append("### Rearchitect Finding Details") - lines.append("") - - for finding in rearchitect_findings: - name = finding.get("name", "") - detected_in = finding.get("detectedIn", {}) or {} - config_files = detected_in.get("configFiles", []) or [] - source_files = detected_in.get("sourceFiles", []) or [] - all_files = config_files + source_files - if not all_files: - continue - anchor_id = make_anchor_id(name) - lines.append(f'
') - lines.append(f"{escape_html(name)} — affected files") - lines.append("") - for file in all_files: - lines.append(f"- `{file}`") - lines.append("") - explanation = finding.get("explanation", "") - if explanation and explanation.strip(): - lines.append(f"**Explanation:** {explanation}") - lines.append("") - lines.append("
") - lines.append("") - - # Security Issues - if security_findings: - lines.append("## Security Issues") - lines.append("") - lines.append("> **Note:** These issues were generated by AI and may contain inaccuracies or incomplete information. Please review carefully.") - lines.append("") - lines.append("| Issue Name | Criticality | Story Points | Files |") - lines.append("|------------|-------------|--------------|-------|") - - ordered_findings = sorted( - security_findings, - key=lambda f: (get_severity_rank((f.get("severity") or "").strip().lower()), -(f.get("storyPoint", 0) or 0)) - ) - - for finding in ordered_findings: - severity = (finding.get("severity") or "").strip().lower() - crit_label = {"mandatory": "Mandatory", "potential": "Potential"}.get(severity, "Optional") - finding_id = finding.get("id", "") - title = finding.get("title", "") - display_name = f"{finding_id}: {title}" if title and title.strip() else finding_id - story_point = finding.get("storyPoint", 0) or 0 - evidence = finding.get("evidence", {}) or {} - files = evidence.get("files", []) or [] - anchor_id = make_anchor_id(display_name) - files_cell = f"[{len(files)}](#{anchor_id})" if files else "0" - lines.append(f"| {escape_table_cell(display_name)} | {crit_label} | {story_point} | {files_cell} |") - - lines.append("") - lines.append("### Security Issue Details") - lines.append("") - - for finding in ordered_findings: - evidence = finding.get("evidence", {}) or {} - files = evidence.get("files", []) or [] - if not files: - continue - finding_id = finding.get("id", "") - title = finding.get("title", "") - display_name = f"{finding_id}: {title}" if title and title.strip() else finding_id - anchor_id = make_anchor_id(display_name) - lines.append(f'
') - lines.append(f"{escape_html(display_name)} — affected files") - lines.append("") - for file in files: - lines.append(f"- `{file}`") - lines.append("") - lines.append("
") - lines.append("") - - lines.append("---") - lines.append("") - - # Codebase Insights - append_codebase_insights(lines, report_dir) - - lines.append("[Share feedback](https://aka.ms/ghcp-appmod/feedback)") - - return '\n'.join(lines) + '\n' - - -def append_codebase_insights(lines: list, report_dir: str): - """Append Codebase Insights section with links to available fact files.""" - fact_docs = [ - ("architecture-diagram.md", "Architecture Diagram", "Understand the big picture: system layers and component relationships"), - ("dependency-map.md", "Dependency Map", "Know what the project depends on and where the risks are"), - ("api-service-contracts.md", "API & Service Contracts", "See how services communicate and what contracts they expose"), - ("data-architecture.md", "Data Architecture", "Explore data models, storage, and data flow patterns"), - ("configuration-inventory.md", "Configuration Inventory", "Review how the application is configured across environments"), - ("business-workflows.md", "Business Workflows", "Trace end-to-end business processes and domain logic"), - ] - - facts_dir = os.path.join(report_dir, "facts") - - lines.append("## Codebase Insights") - lines.append("") - lines.append("> **Note:** These documents are generated by AI and may contain inaccuracies or incomplete information. Please review carefully.") - lines.append("") - - existing = [] - if os.path.isdir(facts_dir): - for filename, title, desc in fact_docs: - if os.path.isfile(os.path.join(facts_dir, filename)): - existing.append((filename, title, desc)) - - if not existing: - lines.append("> **Codebase Insights aren't available yet.**") - lines.append(">") - lines.append("> These documents are generated when assessment runs with **Full analysis** coverage. Re-run the assessment and set `analysisCoverage: full` to enable them.") - lines.append("") - return - - for i, (filename, title, desc) in enumerate(existing, 1): - lines.append(f"{i}. **[{title}](facts/{filename})** — {desc}") - - lines.append("") - - -def render_jsts_report(report_dir: str, ncu_path: str) -> str: - """Render report.md from js-assessment-report.md for JS/TS.""" - with open(ncu_path, 'r', encoding='utf-8') as f: - ncu_output = f.read() - - # Parse ncu output - dependencies = [] - patch_count = 0 - minor_count = 0 - major_count = 0 - zero_major_count = 0 - package_manager = "npm" - current_category = "" - current_category_display = "" - - for line in ncu_output.split('\n'): - trimmed = line.strip() - if trimmed.startswith("Using "): - package_manager = trimmed[len("Using "):].strip() - elif trimmed.startswith("Patch"): - current_category = "patch" - current_category_display = "Patch" - elif trimmed.startswith("Minor"): - current_category = "minor" - current_category_display = "Minor" - elif trimmed.startswith("Major Potentially breaking"): - current_category = "major" - current_category_display = "Major" - elif trimmed.startswith("Major version zero"): - current_category = "zero-major" - current_category_display = "Major (0.x)" - elif trimmed and '→' in trimmed: - arrow_idx = trimmed.index('→') - before_arrow = trimmed[:arrow_idx].strip() - after_arrow = trimmed[arrow_idx + 1:].strip() - - parts = before_arrow.split() - name = ' '.join(parts[:-1]) if len(parts) >= 2 else before_arrow - current_version = parts[-1] if len(parts) >= 2 else "" - - dependencies.append((name, current_version, after_arrow, current_category_display)) - - if current_category == "patch": - patch_count += 1 - elif current_category == "minor": - minor_count += 1 - elif current_category == "major": - major_count += 1 - elif current_category == "zero-major": - zero_major_count += 1 - - total_count = patch_count + minor_count + major_count + zero_major_count - - # Derive repo name from directory name - repo_name = os.path.basename(report_dir) - - lines = [] - lines.append(f"# {repo_name}") - lines.append("") - lines.append("JavaScript/TypeScript Dependency Assessment") - lines.append("") - lines.append("## Application Information") - lines.append("") - lines.append("| Property | Value |") - lines.append("|----------|-------|") - lines.append("| Language | JavaScript/TypeScript |") - lines.append(f"| Build tools | {escape_table_cell(package_manager)} |") - lines.append("") - lines.append("## Summary") - lines.append("") - lines.append("| Update Type | Count |") - lines.append("|-------------|-------|") - lines.append(f"| **Total Updates** | **{total_count}** |") - lines.append(f"| Patch | {patch_count} |") - lines.append(f"| Minor | {minor_count} |") - lines.append(f"| Major | {major_count} |") - if zero_major_count > 0: - lines.append(f"| Major (0.x) | {zero_major_count} |") - lines.append("") - - if dependencies: - lines.append("## Dependency Updates") - lines.append("") - lines.append("| Package | Current | Target | Type |") - lines.append("|---------|---------|--------|------|") - for name, current, target, category in dependencies: - lines.append(f"| {escape_table_cell(name)} | {escape_table_cell(current)} | {escape_table_cell(target)} | {category} |") - lines.append("") - - lines.append("## Recommendations") - lines.append("") - lines.append("| Update Type | Guidance |") - lines.append("|-------------|---------|") - lines.append("| Patch & Minor | Generally safe to apply. Consider updating these first. |") - lines.append("| Major | Review breaking changes in package release notes before updating. |") - lines.append("| Major (0.x) | Exercise caution — these packages follow unstable version semantics. |") - lines.append("") - lines.append("---") - lines.append("") - - append_codebase_insights(lines, report_dir) - - lines.append("[Share feedback](https://aka.ms/ghcp-appmod/feedback)") - - return '\n'.join(lines) + '\n' - - -def main(): - if len(sys.argv) < 2: - print("Usage: python generate_report_md.py ", file=sys.stderr) - sys.exit(1) - - report_dir = sys.argv[1] - - if not os.path.isdir(report_dir): - print(f"Error: Directory not found: {report_dir}", file=sys.stderr) - sys.exit(1) - - report_json_path = os.path.join(report_dir, "report.json") - - if os.path.isfile(report_json_path): - # Java/.NET mode - markdown = render_appcat_report(report_dir, report_json_path) - else: - # JS/TS mode - look for js-assessment-report.md - ncu_path = os.path.join(report_dir, "js-assessment-report.md") - if not os.path.isfile(ncu_path): - # Check parent directory - parent = os.path.dirname(report_dir) - ncu_path = os.path.join(parent, "js-assessment-report.md") - if not os.path.isfile(ncu_path): - print("Error: Neither report.json nor js-assessment-report.md found.", file=sys.stderr) - sys.exit(1) - markdown = render_jsts_report(report_dir, ncu_path) - - if not markdown: - print("Error: Failed to generate report.", file=sys.stderr) - sys.exit(1) - - output_path = os.path.join(report_dir, "report.md") - with open(output_path, 'w', encoding='utf-8') as f: - f.write(markdown) - - print(f"Generated: {output_path}") - - -if __name__ == "__main__": - main() diff --git a/.github/skills/integration-tests/SKILL.md b/.github/skills/integration-tests/SKILL.md deleted file mode 100644 index 0a33aa89a..000000000 --- a/.github/skills/integration-tests/SKILL.md +++ /dev/null @@ -1,324 +0,0 @@ ---- -name: integration-tests -description: Run multi-layer integration tests for modernized Java applications. Supports 4 layers - Layer 1 (TestContainers), Layer 2 (Smoke Tests), Layer 3 (Azure Integration), Layer 4 (Behavioral Comparison). **Java projects only** - skip if source code is not Java. ---- - -## Language Support - -**This skill supports Java projects only.** If the source code is not Java (e.g., .NET, Python, Node.js), skip test generation and report that integration tests are not supported for this language. - -## User Input -- **layer** (Optional): Which layer to test (1, 2, 3, or 4). Default: 1 -- **azure-config** (Optional, Layer 3 only): Azure environment configuration. If not provided, read from `./infra/infra-config.md`. -- **modernization-work-folder** (Optional): Directory path for generating plan and summary files. Default: `.github` -- **test-root** (Optional): The root directory for integration tests. Default: current working directory. All application modules found in the directory are included in integration tests. - -## Available references - -### Layer 1: Local Integration Tests -**Read [references/layer1-local-integration.md](references/layer1-local-integration.md) first**, then create TestContainers-based integration test classes. - -### Layer 2: Smoke Tests -**Read [references/layer2-smoke-tests.md](references/layer2-smoke-tests.md) first.** Layer 2 uses shell-based smoke tests with docker-compose, NOT JUnit test classes. Follow the exact multi-commit workflow (artifacts → auth → restore) documented in the reference file. - -### Layer 3: Azure Integration Tests -**Read [references/layer3-azure-integration.md](references/layer3-azure-integration.md) first**, then create integration test classes that connect to real Azure services. - -### Layer 4: Behavioral Comparison -**Read [references/layer4-behavioral-comparison.md](references/layer4-behavioral-comparison.md) first**, then create comparison tests that validate behavior matches between old and new implementations. - -### TestContainers Coding References -- **Azure Service Bus with TestContainers Coding Reference**, see [references/layer1-servicebus-integration.md](references/azure-servicebus-testcontainers.md) -- **Azure Storage with TestContainers Coding Reference**, see [references/layer1-blobstorage-integration.md](references/azure-storage-testcontainers.md) - -## Workflow - -1. Analyze the project to identify modules that need to be tested and any existing integration tests. If git history is available, analyze past commits to understand which components were modified during modernization and prioritize testing those areas. -2. Create an integration test plan file at `{modernization-work-folder}/integration-tests/integration-test-plan.md` that outlines: - - Testing strategy and approach for the detected app modules - - Testing strategy and approach for each layer - - Identified components requiring integration testing - - Dependencies and test setup requirements - - Expected test scenarios and validation criteria -3. Implement new integration tests following the principles outlined below -4. Execute the tests -5. If issues found: - - Analyze failures using the Test vs Source Code Decision Framework to determine whether to fix test code or source code - - Fix source code if the failure indicates a real problem in the application logic. - - Fix test code if the failure is due to unrealistic test scenarios, incorrect test setup. - - Execute tests again after fixes -6. **Only proceed when all tests run and pass**, or exit after 20 attempts -7. Create an integration test summary file at `{modernization-work-folder}/integration-tests/integration-test-summary.md` that documents: - - All integration tests added (with file paths and descriptions) - - Test coverage improvements achieved - - Final test execution results -8. Commit all code changes with brief and meaningful commit messages - -## Integration Tests Writing Principles - -**CRITICAL - Read Reference Docs First:** -- **Before starting ANY layer**, read the corresponding reference file in [references/](./references/) directory - -Analyze the project if integration tests have covered all components, if not **DO ADD** new integration tests by the following principles: - -- **Purpose:** Ensures combined components function as a whole, focusing on "in-between" logic rather than individual module functionality. -- **DO use** top-down approach to add integration tests. For example, if an application has controller layer, service layer, and database layer. The integration tests should set up real connections to the database, and then test against the controller layer, to validate all functionalities. -- **DO focus on:** Validates interactions between modules, databases, messaging services, and file systems. -- **DO NOT** change the technical stack, architure selection, libary using in the source code. The goal is to validate the existing application code, not change it to pass tests. -- **DO fix issues** in the appropriate place (test or source code) based on the analysis. -- **DO write** comprehensive integration covering **ALL** components. -- **DO test through the application's own classes.** Every test must instantiate and invoke the project's own classes/methods. Never call third-party SDK APIs directly as a substitute for testing application code. Tests that only exercise Azure/Redis/PostgreSQL SDK operations without going through the application's handler/service/manager classes will score very low on functional coverage. -- **DO use** the layer-specific class name suffix from the Test Isolation Convention (e.g., `L1Test` for Layer 1, `L3Test` for Layer 3, `L4Test` for Layer 4). **Layer 2 does not use test classes.** -- **DO annotate** every test class with the layer-specific tag/category from the Test Isolation Convention. -- **DO verify** that `mvn verify` (or equivalent build command) discovers and runs all tests without extra flags. If tests require `-Dtest=...` to be found, the build configuration is wrong. -- **DO NOT** leave commented-out tests or dead test code. Every `@Test` annotation must be on a working test method. -- **DO NOT** add extra modules for integration tests, write integration tests in the existing modules. -- **DO commit** changes separately for each layer with meaningful commit messages. Do not combine changes from different layers into a single commit. - - **Layer 1, 3, 4**: Single commit per layer (e.g., `Add Layer 1 local integration tests`). Generate runner scripts and include them in the same commit. - - **Layer 2**: Multi-commit sequence as defined in [layer2-smoke-tests.md](./references/layer2-smoke-tests.md) (artifacts → auth → restore). **CRITICAL: Layer 2 does NOT create test classes - it uses shell-based smoke tests with docker-compose.** Runner scripts are part of the artifacts commit. - - -### Test Isolation Convention - -When multiple layers coexist in the same project, tests must be distinguishable. **Every layer MUST use a distinct class name suffix AND tag/category** so tests from different layers never interfere with each other. - -#### Naming Convention - -| Layer | Class Name Suffix | Example Class Name | -|-------|-------------------|--------------------| -| 1 | `L1Test` | `BlobStorageL1Test`, `OrderServiceL1Test` | -| 2 | N/A - No test classes | Layer 2 uses shell-based smoke tests, not test classes. See [layer2-smoke-tests.md](./references/layer2-smoke-tests.md) | -| 3 | `L3Test` | `AzureSqlL3Test`, `BlobStorageL3Test` | -| 4 | `L4Test` | `OrderApiL4Test`, `UserServiceL4Test` | - -#### Tagging / Category Convention - -Test classes for Layers 1, 3, 4 **MUST** be annotated with a layer-specific tag so the runner script can filter precisely. **Layer 2 does not use test classes** (see [layer2-smoke-tests.md](./references/layer2-smoke-tests.md)). - -| Layer | JUnit 5 | JUnit 4 | -|-------|---------|---------| -| 1 | `@Tag("Layer1")` | `@Category(Layer1.class)` | -| 2 | N/A - Shell-based | N/A - Shell-based | -| 3 | `@Tag("Layer3")` | `@Category(Layer3.class)` | -| 4 | `@Tag("Layer4")` | `@Category(Layer4.class)` | - -> **Rule**: Never rely solely on class name patterns for filtering. Always use tags/categories as the primary filter mechanism. - -### NEVER Mock the Migrated Service - -**This is the most important rule in this entire skill — it applies to ALL layers.** - -Integration tests MUST use real TestContainers for layer 1 and 2 and real cloud services for Layer 3 for every migrated dependency. Do NOT mock or `@MockBean` the SDK client that the migrated code uses (e.g., `ServiceBusTemplate`, `ServiceBusSenderClient`, `BlobServiceClient`, `RedisTemplate`, `DataSource`). Instead, spin up a real container and wire the application's own service to it. - -#### The Anti-Pattern You MUST Avoid - -```java -// ❌ WRONG — mocks the migrated service and SDK clients, tests unchanged code instead -@SpringBootTest -@ActiveProfiles("test") -class MyAppL1Test { - @MockBean private MessageService messageService; // THE MIGRATED CLASS — never tested! - @MockBean private ServiceBusSenderClient senderClient; // should be from real emulator - @MockBean private TokenCredential tokenCredential; // should be from emulator config - @Autowired private UserRepository userRepo; // unchanged code - - @Test void testUserCrud() { - userRepo.save(new User("test")); // tests unchanged code, NOT the migration - } -} -``` - -```java -// ✅ RIGHT — uses real Service Bus emulator, ALL beans wired from containers, tests through migrated service -@SpringBootTest -@ActiveProfiles("test") -@Testcontainers -class MyAppL1Test { - @Container static final GenericContainer SERVICE_BUS = ...; // real emulator - // No @MockBean at all — all beans come from containers + @DynamicPropertySource - @Autowired private MessageService messageService; // THE MIGRATED CLASS — tested for real! - - @DynamicPropertySource - static void props(DynamicPropertyRegistry registry) { - registry.add("spring.cloud.azure.servicebus.connection-string", () -> getConnectionString()); - // ... other container-derived properties - } - - @Test void testSendMessage() { - messageService.sendMessage("queue", "Hello"); // exercises the actual migrated code - } -} -``` - -## Handling Test Failures - -When integration tests fail during execution, use this framework to determine whether to fix the test code or the source code: - -### Fix Source Code When: - -**Business Logic Violations** -- Error indicates source code violates business rules (e.g., negative inventory allowed) -- Multiple similar tests fail with same pattern -**Specification Compliance** -- Source code doesn't implement required functionality properly -- Error messages show missing or incorrect behavior -**Cross-Component Integration Issues** -- Test setup is correct and realistic -- Source code fails to properly communicate between modules -- Data transformation or mapping errors between layers -**Resource Management Problems** -- Test uses proper connection/resource patterns -- Source code has leaks, deadlocks, or improper disposal -- Timing issues in source code (not test race conditions) - -### Fix Test Code When: - -**Test Implementation Issues** -- Unrealistic test data or scenarios -- Incorrect test setup (wrong mocks, invalid configurations) -- Testing implementation details rather than behavior -- Race conditions or timing issues in test logic -**Environmental Problems** -- Wrong container configurations or versions -- Test dependencies not properly isolated -- Hard-coded values that should be configurable -- Test cleanup issues affecting subsequent tests -**Test Design Flaws** -- Tests making too many assumptions about internal state -- Over-mocking leading to false confidence -- Testing edge cases that don't reflect real usage -- Assertions on wrong data or wrong timing - -### Analysis Steps: - -1. **Review Test Quality**: Does the test follow established patterns and realistic scenarios? -2. **Check Business Logic**: Does the failure indicate business rule violations in source code? -3. **Verify Setup**: Are test dependencies and configurations realistic and correct? -4. **Assess Error Type**: Is it a logic error, integration error, or test infrastructure issue? -5. **Consider Impact**: Would fixing source code improve real application behavior? - -### Decision Process: - -``` -Test Failure - │ - ├─ Does test model realistic business scenario? - │ ├─ No → Fix Test Code - │ └─ Yes ↓ - │ - ├─ Does source code violate business rules? - │ ├─ Yes → Fix Source Code - │ └─ No ↓ - │ - ├─ Is test setup and environment correct? - │ ├─ No → Fix Test Code - │ └─ Yes ↓ - │ - └─ Does error show integration/logic problem? - ├─ Yes → Fix Source Code - └─ No → Fix Test Code -``` - -## Standardized Runner Scripts - -After all tests are written, executed, and fixed to pass, generate a fixed runner script so users can re-run integration tests with a single command regardless of project type. - -| Layer | Script Path | Command (Unix) | Command (Windows) | -|-------|-------------|----------------|--------------------|| -| 1 | `{modernization-work-folder}/integration-tests/run-layer1-tests.sh` / `.ps1` | `bash {modernization-work-folder}/integration-tests/run-layer1-tests.sh` | `powershell {modernization-work-folder}/integration-tests/run-layer1-tests.ps1` | -| 2 | `{modernization-work-folder}/integration-tests/run-layer2-tests.sh` / `.ps1` | `bash {modernization-work-folder}/integration-tests/run-layer2-tests.sh` | `powershell {modernization-work-folder}/integration-tests/run-layer2-tests.ps1` | -| 3 | `{modernization-work-folder}/integration-tests/run-layer3-tests.sh` / `.ps1` | `bash {modernization-work-folder}/integration-tests/run-layer3-tests.sh` | `powershell {modernization-work-folder}/integration-tests/run-layer3-tests.ps1` | -| 4 | `{modernization-work-folder}/integration-tests/run-layer4-tests.sh` / `.ps1` | `bash {modernization-work-folder}/integration-tests/run-layer4-tests.sh` | `powershell {modernization-work-folder}/integration-tests/run-layer4-tests.ps1` | - -### Runner Script Requirements - -1. **Always generate both `.sh` and `.ps1` variants** for cross-platform support. -2. The script **MUST** encapsulate all project-specific details (build tool, test runner, filters, working directory, container setup/teardown). -3. The script **MUST** be self-contained — users should not need to know the build system or test framework to run it. -4. The script **MUST** exit with code 0 on success and non-zero on failure. -5. The script **MUST** print a human-readable result summary at the end: - - **On success**: `✅ Layer 1 integration tests PASSED` - - **On failure**: `❌ Layer 1 integration tests FAILED` - - The test runner's own console output already includes detailed counts, failure logs, assertion messages, and stack traces — the script only needs a clear pass/fail signal at the end. -6. For Layer 1, the script **MUST** verify Docker is running before starting tests. -7. For Layer 2, the script **MUST** handle starting and stopping the application. -8. For Layer 3, the script **SHOULD** accept Azure configuration via environment variables. -9. For Layer 4, the script **MUST** handle starting both old and new application versions. - -### Runner Script Filtering - -**Layers 1, 3, 4** use tag/category filters to execute test classes. **Layer 2 uses shell commands** (see [layer2-runner-script-templates.md](./references/layer2-runner-script-templates.md)). - -| Layer | Maven | Gradle | -|-------|-------|--------| -| 1 | `mvn verify -Dgroups=Layer1` | `./gradlew test -Dgroups=Layer1` | -| 2 | Shell-based smoke tests | Shell-based smoke tests | -| 3 | `mvn verify -Dgroups=Layer3` | `./gradlew test -Dgroups=Layer3` | -| 4 | `mvn verify -Dgroups=Layer4` | `./gradlew test -Dgroups=Layer4` | - -### Example Runner Script Structure (Layer 1) - -```bash -#!/bin/bash -set -euo pipefail - -# --- Auto-generated integration test runner --- -# Project type: -# Generated on: - -# Verify prerequisites -if ! docker info > /dev/null 2>&1; then - echo "ERROR: Docker is not running. Please start Docker and try again." - exit 1 -fi - -# Run integration tests (project-specific command is embedded here) -# IMPORTANT: Always filter by the layer-specific tag to avoid running other layers' tests -# Test output (including any failure logs) goes directly to the console. -cd "$(dirname "$0")/../.." - - -# e.g., mvn verify -Dgroups=Layer1, ./gradlew test -Dgroups=Layer1 - -TEST_EXIT=$? - -# --- Print summary --- -echo "" -echo "========================================" -if [ $TEST_EXIT -eq 0 ]; then - echo "✅ Layer 1 integration tests PASSED" -else - echo "❌ Layer 1 integration tests FAILED" -fi -echo "========================================" -exit $TEST_EXIT -``` - -> **Note to implementers:** Replace `` with the real test command for the detected project type (Maven/Gradle). The test runner's own console output already includes detailed results — the script just appends a clear pass/fail signal at the end. - -## Completion Criteria - -1. **Integration Test Plan**: Create and output a plan file at `{modernization-work-folder}/integration-tests/integration-test-plan.md` that includes: - - Analysis of existing test coverage gaps - - Identified components requiring integration testing - - Testing strategy and approach for each component - - Dependencies and test setup requirements - - Expected test scenarios and validation criteria -2. All tests for the requested layer **run and pass**, show the running results to the user -3. Test results are reported with clear pass/fail status -4. Any failures are properly analyzed and resolved (see Handling Test Failures section) -5. Test artifacts (logs, screenshots, comparison reports) are saved - -6. **Version Control**: Commit changes separately for each layer with meaningful commit messages. Do not combine changes from different layers into a single commit. - - **Layer 1, 3, 4**: Single commit per layer including test classes and runner scripts (e.g., `Add Layer 1 local integration tests`) - - **Layer 2**: Multi-commit sequence as defined in [layer2-smoke-tests.md](./references/layer2-smoke-tests.md) (minimum 3 commits: artifacts → auth → restore). Runner scripts are part of the artifacts commit. - - **Git ignore respect**: Use standard `git add` commands. Do not force-add files. If files in `{modernization-work-folder}` are ignored by the project's `.gitignore`, respect that. - -7. **Integration Test Summary**: Create and output a summary file at `{modernization-work-folder}/integration-tests/integration-test-summary.md` that documents: - - All integration tests added (with file paths and descriptions) - - Test coverage improvements achieved - - Issues identified and resolved (both in source code and test code) - - Final test execution results - - Paths to generated runner scripts and the fixed commands to execute them - - Source code changes made during testing and their purpose -8. **Runner Scripts**: Generate standardized runner scripts at `{modernization-work-folder}/integration-tests/run-layer{N}-tests.sh` and `.ps1` (see Standardized Runner Scripts section). The scripts must embed all project-specific commands so users always run the same fixed command. Include runner scripts in the layer's commit (for Layer 2, in the artifacts commit). diff --git a/.github/skills/integration-tests/references/azure-auth-strategies.md b/.github/skills/integration-tests/references/azure-auth-strategies.md deleted file mode 100644 index 5300d4636..000000000 --- a/.github/skills/integration-tests/references/azure-auth-strategies.md +++ /dev/null @@ -1,189 +0,0 @@ -# Azure Authentication Strategies for Smoke Tests - -Migrated applications typically use **Managed Identity** (`DefaultAzureCredential`) which only works on Azure infrastructure. For local smoke testing, applications must connect to local emulators or have Azure services disabled. - -## Azure Dependency Classification - -Classify each Azure dependency into one of three categories: - -| Category | Smoke Test Strategy | Example Services | -|----------|---------------------|------------------| -| **Emulatable** | Use local emulator with connection string auth | Azurite, Cosmos Emulator, Service Bus Emulator, Event Hubs Emulator | -| **Lazy** | Skip — not validated at startup | On-demand blob uploads, external REST APIs | -| **Startup-required, non-emulatable** | Disable via config, or recommend Layer 3 | Key Vault, App Configuration | - -### Emulatable Azure Services - -These services have official emulators that can run in Docker containers: - -| Azure Service | Emulator Image | Dependencies | -|---------------|----------------|--------------| -| Blob / Queue / Table Storage | `mcr.microsoft.com/azure-storage/azurite` | None | -| Cosmos DB | `mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator` | None (but requires SSL trust store + endpoint/key auth) | -| SQL Database | `mcr.microsoft.com/mssql/server` | None (wire-compatible with SQL Server) | -| Service Bus | `mcr.microsoft.com/azure-messaging/servicebus-emulator` | Requires MSSQL companion + JSON config | -| Event Hubs | `mcr.microsoft.com/azure-messaging/eventhubs-emulator` | Requires Azurite companion + JSON config | - -> **Note:** Service Bus and Event Hubs emulators require companion containers and a JSON config file. See [azure-servicebus-testcontainers.md](./azure-servicebus-testcontainers.md) for the config format. - -**Cosmos DB emulator note:** Unlike other emulators which use connection strings, the Cosmos DB emulator authenticates via **endpoint + account key** and requires an **SSL certificate** imported into a trust store (e.g., `javax.net.ssl.trustStore` for Java). If SSL setup is too complex, treat Cosmos DB as non-emulatable and recommend Layer 3. - -### Lazy Azure Connections - -If an Azure client is only used inside request handlers or scheduled jobs (no `@PostConstruct` / `IHostedService.StartAsync` / health check probing it), it's lazy. **Skip it** — the app starts fine without it. - -**How to identify:** -- Client is injected but never called during startup -- Client is used only in REST endpoint handlers -- Client is used in background jobs that start after initialization - -### Startup-Required, Non-Emulatable Services - -Some Azure services are required at startup but have no local emulator. - -**Resolution strategies (in priority order):** - -1. **Disable via config** — e.g., `spring.cloud.azure.keyvault.secret.enabled=false` or `KeyVault__Enabled=false` with an `if` guard around registration. -2. **Stub the health check** — Skip that health indicator in the smoke profile. -3. **Accept partial startup** — Non-fatal errors are OK for Layer 2 if the app reaches a healthy state. -4. **Skip Layer 2, recommend Layer 3** — If the app cannot start without a real Azure connection. - -## Authentication Modification Strategies - -For each emulatable Azure dependency, modify how the application authenticates: - -### Config-Driven Applications - -**Examples:** Spring Boot applications with `application.properties` or `application.yml`, Quarkus/Micronaut applications with `application.properties` - -**When to use:** Applications that use a configuration framework to externalize settings. These apps read connection strings from config files at runtime. - -**Strategy:** Modify config files to add explicit emulator connection strings. Create or modify the main config file (NOT environment variables, NOT test-only config files). - -**Which config file to modify:** -- Spring Boot: `src/main/resources/application.properties` or `src/main/resources/application.yml` -- Quarkus: `src/main/resources/application.properties` - -**CRITICAL:** You MUST always modify the config file to add the explicit connection string value. Do NOT rely on environment variables. - -**Example:** - -`src/main/resources/application.properties`: -```properties -azure.storage.blob.connection-string=DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://localhost:10000/devstoreaccount1; -spring.data.mongodb.uri=mongodb://localhost:27017/mydb -``` - -**If the application code uses `@Bean` methods or manual client construction:** You may also need to modify the code to read these config properties if it doesn't already. Add code to accept the connection string property with fallback to the original auth. Then add the connection string value to the config file as shown above. - -### Hardcoded/Plain Applications - -**Examples:** Plain Java CLI apps without config frameworks - -**When to use:** Applications that have NO configuration framework (no Spring Boot, no application.properties). These apps construct clients directly in code with hardcoded values. - -**Strategy:** Modify the source code to hardcode emulator connection strings directly in the client construction code. Since these applications don't have config files to modify, the connection string must be in the source code itself to be committed to git. - -**Why hardcoding is required:** -- No config framework means no config files to modify -- Environment variables aren't committed to git, so they won't be reverted by the restore commit -- The auth commit must contain a reversible change in tracked files - -**Java example:** - -**Before:** -```java -public class BlobServiceClientProvider { - private static BlobServiceClient blobServiceClient; - - public static BlobServiceClient getClient() { - if (blobServiceClient == null) { - String endpoint = System.getenv("AZURE_STORAGE_ENDPOINT"); - blobServiceClient = new BlobServiceClientBuilder() - .endpoint(endpoint) - .credential(new DefaultAzureCredentialBuilder().build()) - .buildClient(); - } - return blobServiceClient; - } -} -``` - -**After:** -```java -public class BlobServiceClientProvider { - private static BlobServiceClient blobServiceClient; - - public static BlobServiceClient getClient() { - if (blobServiceClient == null) { - // Hardcoded connection string for smoke testing with Azurite emulator - String smokeTestConnectionString = "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://localhost:10000/devstoreaccount1;"; - - if (smokeTestConnectionString != null && !smokeTestConnectionString.isEmpty()) { - blobServiceClient = new BlobServiceClientBuilder() - .connectionString(smokeTestConnectionString) - .buildClient(); - } else { - // Fallback to original Managed Identity auth - String endpoint = System.getenv("AZURE_STORAGE_ENDPOINT"); - blobServiceClient = new BlobServiceClientBuilder() - .endpoint(endpoint) - .credential(new DefaultAzureCredentialBuilder().build()) - .buildClient(); - } - } - return blobServiceClient; - } -} -``` - -**CRITICAL:** Only the source code changes are included in the auth commit. No config file changes because there are no config files. - -### Non-Emulatable Services - -**Strategy:** Disable via config or conditional guards: - -**Spring Boot example:** -```yaml -# application-smoke.yml -spring: - cloud: - azure: - keyvault: - secret: - enabled: false -``` - -If the app cannot start without the service, recommend Layer 3 (Azure integration tests). - -## Emulator Connection String Values - -Since `docker-compose.smoke.yml` uses **fixed host port mappings**, each emulator's connection string is a **static constant** — deterministic at generation time. - -| Service | Auth Type | How to Determine the Value | -|---------|-----------|----------------------------| -| Azurite (Blob/Queue/Table) | Connection string | Built from well-known account `devstoreaccount1`, well-known key, and mapped ports (10000/10001/10002). See [Azurite docs](https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite#well-known-storage-account-and-key). | -| Service Bus | Connection string | Built from well-known SAS key + `UseDevelopmentEmulator=true`. See [Service Bus emulator docs](https://learn.microsoft.com/en-us/azure/service-bus-messaging/test-locally-with-service-bus-emulator). | -| Event Hubs | Connection string | Same pattern as Service Bus. See [Event Hubs emulator docs](https://learn.microsoft.com/en-us/azure/event-hubs/test-locally-with-event-hub-emulator). | -| Cosmos DB | Endpoint + key | Built from well-known emulator key + mapped port. See [Cosmos emulator docs](https://learn.microsoft.com/en-us/azure/cosmos-db/emulator). Also requires SSL trust store setup. | - -**Important:** The agent must look up the **actual current default credentials** from the linked official docs at generation time. Emulator defaults may change across versions. Do NOT hardcode connection strings from this file. - -## Decision Flow - -``` -For each Azure dependency: -├─ Has local emulator? → Add to docker-compose.smoke.yml → Modify auth to use emulator -├─ Lazy / on-demand? → Skip. Done. -├─ Can disable via config? → Disable in config override or code modification -└─ None of above → Report: "Layer 2 not feasible — use Layer 3" -``` - -## Modification Rules - -When modifying application code or config to use emulators: - -1. **Minimal changes only** — Only modify what's needed to switch auth from Managed Identity to emulator connection strings. Do not refactor, rename, or restructure. -2. **No new dependencies** — Use only packages already in the project. -3. **Reversible** — Changes should be easily revertable to restore original Managed Identity auth. -4. **No unrelated changes** — Never make refactoring, cleanup, or feature changes alongside auth modifications. diff --git a/.github/skills/integration-tests/references/azure-servicebus-testcontainers.md b/.github/skills/integration-tests/references/azure-servicebus-testcontainers.md deleted file mode 100644 index 50b596322..000000000 --- a/.github/skills/integration-tests/references/azure-servicebus-testcontainers.md +++ /dev/null @@ -1,688 +0,0 @@ -# Azure Service Bus Emulator TestContainers Coding Reference - -## Contents -- 1. MANDATORY - Choose Approach based on Spring/non-Spring, Spring Boot version, TestContainers version -- 2. MANDATORY - Scan all queues/topics/subscriptions before writing code -- 3. DO NOT mock -- 4. Example of Service Bus Emulator config.json -- 5. Spring Cloud Azure Version Compatibility -- 6. Emulator with ServiceBusEmulatorContainer (testcontainers >= 1.20.5) - + Setting up a network - + Starting a SQL Server container as dependency - + Starting a Service Bus Emulator container - + Using Azure Service Bus clients -- 7. Emulator with GenericContainer (testcontainers < 1.20.5) -- 8. Using Spring Cloud Azure with @ServiceConnection (Spring Boot >= 3.1) - + Container setup with @ServiceConnection - + Spring messaging pattern (ServiceBusTemplate / ServiceBusSenderClient) -- 9. Spring Boot 2.x Pattern -- 10. Handle ServiceBusAdministrationClient -- 11. Emulator Readiness and Retry -- 12. Emulator fails to start -- 13. Common Gotchas - -## MANDATORY - Choose Approach based on Spring/non-Spring, Spring Boot version, TestContainers version - -**You MUST check the project's testcontainers and Spring Boot versions BEFORE writing any Service Bus test code.** Using the wrong approach wastes significant time on compilation and runtime failures that are impossible to fix. - -- If project contains usage of `ServiceBusAdministrationClient`, use Service Bus emulator -- If Spring Boot **>= 3.1**, use `@ServiceConnection` for auto-wiring: [Using Spring Cloud Azure with @ServiceConnection](#using-spring-cloud-azure-with-serviceconnection-spring-boot--31) -- If Spring Boot **2.x**, use `@DynamicPropertySource` — `@ServiceConnection` is NOT available: [Spring Boot 2.x pattern](#spring-boot-2x-pattern) -- If testcontainers **>= 1.20.5**, use `ServiceBusEmulatorContainer` from `org.testcontainers:azure`: [Emulator with ServiceBusEmulatorContainer](#emulator-with-servicebusemulatorcontainer-testcontainers--1205) -- If testcontainers **< 1.20.5**, use `GenericContainer` with the emulator Docker image: [Emulator with GenericContainer](#emulator-with-genericcontainer-testcontainers--1205) - -**CRITICAL: `ServiceBusEmulatorContainer` does NOT exist in testcontainers before 1.20.5.** If you see `ClassNotFoundException` or `cannot find symbol: class ServiceBusEmulatorContainer`, the project's testcontainers version is too old. Switch to the GenericContainer approach or upgrade testcontainers. The Maven artifact is `org.testcontainers:azure` (NOT `testcontainers-azure`). - -## MANDATORY - Scan all queues/topics/subscriptions before writing code -The Service Bus emulator cannot create Service Bus queues/topics/subscriptions on the fly, so every resource need to be declared in the config.json. So scan the project to see what queues/topics/subscriptions need to be declared. And then define them in config.json. - -## Do NOT mock -**NEVER MOCK** the `ServiceBusSenderClient`, `ServiceBusTemplate`, `ServiceBusReceiverClient`, `ServiceBusProcessorClient`, `TokenCredential`, `ServiceBusAdministrationClient`, or **ANY bean related to the migrated service.** - -## Example of Service Bus Emulator config.json -Place this file at `src/test/resources/config.json` (or `service-bus-config.json`): - -```json -{ - "UserConfig": { - "Namespaces": [ - { - "Name": "sbemulatorns", - "Queues": [ - { - "Name": "queue.1", - "Properties": { - "DeadLetteringOnMessageExpiration": false, - "DefaultMessageTimeToLive": "PT1H", - "DuplicateDetectionHistoryTimeWindow": "PT20S", - "ForwardDeadLetteredMessagesTo": "", - "ForwardTo": "", - "LockDuration": "PT1M", - "MaxDeliveryCount": 3, - "RequiresDuplicateDetection": false, - "RequiresSession": false - } - } - ], - - "Topics": [ - { - "Name": "topic.1", - "Properties": { - "DefaultMessageTimeToLive": "PT1H", - "DuplicateDetectionHistoryTimeWindow": "PT20S", - "RequiresDuplicateDetection": false - }, - "Subscriptions": [ - { - "Name": "subscription.1", - "Properties": { - "DeadLetteringOnMessageExpiration": false, - "DefaultMessageTimeToLive": "PT1H", - "LockDuration": "PT1M", - "MaxDeliveryCount": 3, - "ForwardDeadLetteredMessagesTo": "", - "ForwardTo": "", - "RequiresSession": false - }, - "Rules": [ - { - "Name": "app-prop-filter-1", - "Properties": { - "FilterType": "Correlation", - "CorrelationFilter": { - "ContentType": "application/text", - "CorrelationId": "id1", - "Label": "subject1", - "MessageId": "msgid1", - "ReplyTo": "someQueue", - "ReplyToSessionId": "sessionId", - "SessionId": "session1", - "To": "xyz" - } - } - } - ] - }, - { - "Name": "subscription.2", - "Properties": { - "DeadLetteringOnMessageExpiration": false, - "DefaultMessageTimeToLive": "PT1H", - "LockDuration": "PT1M", - "MaxDeliveryCount": 3, - "ForwardDeadLetteredMessagesTo": "", - "ForwardTo": "", - "RequiresSession": false - }, - "Rules": [ - { - "Name": "user-prop-filter-1", - "Properties": { - "FilterType": "Correlation", - "CorrelationFilter": { - "Properties": { - "prop1": "value1" - } - } - } - } - ] - }, - { - "Name": "subscription.3", - "Properties": { - "DeadLetteringOnMessageExpiration": false, - "DefaultMessageTimeToLive": "PT1H", - "LockDuration": "PT1M", - "MaxDeliveryCount": 3, - "ForwardDeadLetteredMessagesTo": "", - "ForwardTo": "", - "RequiresSession": false - } - }, - { - "Name": "subscription.4", - "Properties": { - "DeadLetteringOnMessageExpiration": false, - "DefaultMessageTimeToLive": "PT1H", - "LockDuration": "PT1M", - "MaxDeliveryCount": 3, - "ForwardDeadLetteredMessagesTo": "", - "ForwardTo": "", - "RequiresSession": false - }, - "Rules": [ - { - "Name": "sql-filter-1", - "Properties": { - "FilterType": "Sql", - "SqlFilter": { - "SqlExpression": "sys.MessageId = '123456' AND userProp1 = 'value1'" - }, - "Action" : { - "SqlExpression": "SET sys.To = 'Entity'" - } - } - } - ] - } - ] - } - ] - } - ], - "Logging": { - "Type": "File" - } - } -} -``` - -## Spring Cloud Azure Version Compatibility - -Pick the Spring Cloud Azure version compatible with the project's Spring Boot version. See [aka.ms/spring/versions](https://aka.ms/spring/versions) for the compatibility matrix. - -| Spring Boot | Spring Cloud Azure | Notes | -|---|---|---| -| 2.x | 4.x | No `@ServiceConnection`, no `spring-cloud-azure-testcontainers`. Use `@DynamicPropertySource`. | -| 3.1.x - 3.5.x | 5.x | `@ServiceConnection` available | -| 4.0.x | 7.x | `@ServiceConnection` available | - -## Emulator with ServiceBusEmulatorContainer (testcontainers >= 1.20.5) - -**Requires: `org.testcontainers:azure` version >= 1.20.5** - -```xml - - org.testcontainers - azure - 1.20.5 - test - - - org.testcontainers - mssqlserver - test - -``` - -### Setting up a network - -```java -Network network = Network.newNetwork(); -``` - -### Starting a SQL Server container as dependency - -```java -MSSQLServerContainer mssqlServerContainer = new MSSQLServerContainer<>( - "mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04" -) - .acceptLicense() - .withPassword("yourStrong(!)Password") - .withCreateContainerCmdModifier(cmd -> { - cmd.getHostConfig().withCapAdd(Capability.SYS_PTRACE); - }) - .withNetwork(network); -``` - -### Starting a Service Bus Emulator container - -```java -ServiceBusEmulatorContainer emulator = new ServiceBusEmulatorContainer( - "mcr.microsoft.com/azure-messaging/servicebus-emulator:1.1.2" -) - .acceptLicense() - .withConfig(MountableFile.forClasspathResource("/service-bus-config.json")) - .withNetwork(network) - .withMsSqlServerContainer(mssqlServerContainer); -``` - -### Using Azure Service Bus clients - -```java -ServiceBusSenderClient senderClient = new ServiceBusClientBuilder() - .connectionString(emulator.getConnectionString()) - .sender() - .queueName("queue.1") - .buildClient(); - -ServiceBusProcessorClient processorClient = new ServiceBusClientBuilder() - .connectionString(emulator.getConnectionString()) - .processor() - .queueName("queue.1") - .processMessage(messageConsumer) - .processError(errorConsumer) - .buildProcessorClient(); -``` - -## Emulator with GenericContainer (testcontainers < 1.20.5) - -**Use this when the project's testcontainers version does not include `ServiceBusEmulatorContainer`.** This approach uses `GenericContainer` directly with the same emulator Docker image. - -```xml - - org.testcontainers - testcontainers - test - - - org.testcontainers - junit-jupiter - test - - - org.testcontainers - mssqlserver - test - -``` - -```java -@Testcontainers -@Tag("Layer1") -class ServiceBusEmulatorL1Test { - - static final Network NETWORK = Network.newNetwork(); - - @Container - static final MSSQLServerContainer SQL_SERVER = new MSSQLServerContainer<>( - "mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04") - .acceptLicense() - .withPassword("yourStrong(!)Password") - .withNetwork(NETWORK) - .withNetworkAliases("mssql"); - - @Container - static final GenericContainer SERVICE_BUS = new GenericContainer<>( - "mcr.microsoft.com/azure-messaging/servicebus-emulator:1.1.2") - .withExposedPorts(5672) - .withNetwork(NETWORK) - .withEnv("ACCEPT_EULA", "Y") - .withEnv("MSSQL_SA_PASSWORD", "yourStrong(!)Password") - .withEnv("SQL_SERVER", "mssql") // network alias of the SQL Server container - .withCopyFileToContainer( - MountableFile.forClasspathResource("config.json"), - "/ServiceBus_Emulator/ConfigFiles/config.json") - .dependsOn(SQL_SERVER) - .waitingFor(Wait.forLogMessage(".*Emulator Service is Successfully Up!.*", 1) - .withStartupTimeout(Duration.ofMinutes(3))); - - static String getConnectionString() { - return String.format( - "Endpoint=sb://%s:%d;SharedAccessKeyName=RootManageSharedAccessKey;" - + "SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;", - SERVICE_BUS.getHost(), SERVICE_BUS.getMappedPort(5672)); - } - - private ServiceBusSenderClient senderClient; - private ServiceBusReceiverClient receiverClient; - - @BeforeAll - static void waitForEmulator() { - // Emulator needs extra time after port is ready for entities to initialize - // Use Awaitility — NEVER Thread.sleep() - Awaitility.await() - .atMost(Duration.ofSeconds(120)) - .pollInterval(Duration.ofSeconds(2)) - .until(() -> { - try { - ServiceBusSenderClient probe = new ServiceBusClientBuilder() - .connectionString(getConnectionString()) - .sender().queueName("queue.1").buildClient(); - probe.sendMessage(new ServiceBusMessage("probe")); - probe.close(); - return true; - } catch (Exception e) { - return false; - } - }); - } - - @BeforeEach - void setupClients() { - senderClient = new ServiceBusClientBuilder() - .connectionString(getConnectionString()) - .sender().queueName("queue.1").buildClient(); - receiverClient = new ServiceBusClientBuilder() - .connectionString(getConnectionString()) - .receiver().queueName("queue.1").buildClient(); - } - - @AfterEach - void closeClients() { - if (senderClient != null) senderClient.close(); - if (receiverClient != null) receiverClient.close(); - } -} -``` - -## Using Spring Cloud Azure with @ServiceConnection (Spring Boot >= 3.1) - -**Requires:** Spring Boot >= 3.1, `spring-cloud-azure-testcontainers`, testcontainers >= 1.20.5. - -`@ServiceConnection` is a Spring Boot 3.1+ feature. **Do NOT use this with Spring Boot 2.x — it will not compile.** For Spring Boot 2.x, see the [Spring Boot 2.x pattern](#spring-boot-2x-pattern) section. - -```xml - - - 5.25.0 - - - - - - com.azure.spring - spring-cloud-azure-dependencies - ${version.spring.cloud.azure} - pom - import - - - - - - - - com.azure.spring - spring-messaging-azure-servicebus - - - - - com.azure.spring - spring-cloud-azure-starter - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.boot - spring-boot-testcontainers - test - - - org.testcontainers - junit-jupiter - test - - - com.azure.spring - spring-cloud-azure-testcontainers - test - - - com.microsoft.sqlserver - mssql-jdbc - test - - -``` - -### Container setup with @ServiceConnection - -`@ServiceConnection` tells Spring Boot to auto-configure `AzureServiceBusConnectionDetails` from the running container — no manual connection string wiring needed. The `MSSQLServerContainer` is NOT annotated with `@Container` because `ServiceBusEmulatorContainer.withMsSqlServerContainer()` manages its lifecycle. - -```java -private static final Network NETWORK = Network.newNetwork(); - -private static final MSSQLServerContainer SQLSERVER = new MSSQLServerContainer<>( - "mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04") - .acceptLicense() - .withNetwork(NETWORK) - .withNetworkAliases("sqlserver"); - -@Container -@ServiceConnection -private static final ServiceBusEmulatorContainer SERVICE_BUS = new ServiceBusEmulatorContainer( - "mcr.microsoft.com/azure-messaging/servicebus-emulator:latest") - .acceptLicense() - .withCopyFileToContainer(MountableFile.forClasspathResource("config.json"), - "/ServiceBus_Emulator/ConfigFiles/config.json") - .withNetwork(NETWORK) - .withMsSqlServerContainer(SQLSERVER); -``` - -### Spring messaging pattern (ServiceBusTemplate / ServiceBusSenderClient) - -Use when the application uses `ServiceBusSenderClient` or `ServiceBusTemplate` for sending, and `ServiceBusRecordMessageListener` for processing. - -```java -@SpringJUnitConfig -@TestPropertySource(properties = { - "spring.cloud.azure.servicebus.entity-name=queue.1", - "spring.cloud.azure.servicebus.entity-type=queue" -}) -@Testcontainers -@Tag("Layer1") -class ServiceBusMessagingL1Test { - - // ... container setup as above ... - - @Autowired - private ServiceBusSenderClient senderClient; - - @Autowired - private ServiceBusTemplate serviceBusTemplate; - - @Test - void senderClientCanSendAndReceiveMessage() { - // The emulator depends on SQL Server and needs time to initialize messaging entities - waitAtMost(Duration.ofSeconds(120)).pollInterval(Duration.ofSeconds(2)).untilAsserted(() -> { - senderClient.sendMessage(new ServiceBusMessage("Hello World!")); - }); - - waitAtMost(Duration.ofSeconds(30)).untilAsserted(() -> { - assertThat(Config.MESSAGES).contains("Hello World!"); - }); - } - - @Test - void serviceBusTemplateCanSendAndReceiveMessage() { - waitAtMost(Duration.ofSeconds(120)).pollInterval(Duration.ofSeconds(2)).untilAsserted(() -> { - serviceBusTemplate.sendAsync("queue.1", - MessageBuilder.withPayload("Hello from template!").build()) - .block(Duration.ofSeconds(10)); - }); - - waitAtMost(Duration.ofSeconds(30)).untilAsserted(() -> { - assertThat(Config.MESSAGES).contains("Hello from template!"); - }); - } - - @Configuration(proxyBeanMethods = false) - @ImportAutoConfiguration(classes = { - AzureGlobalPropertiesAutoConfiguration.class, - AzureServiceBusAutoConfiguration.class, - AzureServiceBusMessagingAutoConfiguration.class}) - static class Config { - - private static final Set MESSAGES = ConcurrentHashMap.newKeySet(); - - @Bean - ServiceBusRecordMessageListener processMessage() { - return context -> MESSAGES.add(context.getMessage().getBody().toString()); - } - - @Bean - ServiceBusErrorHandler errorHandler() { - return (context) -> { }; - } - } -} -``` - -## Spring Boot 2.x Pattern - -**Spring Boot 2.x does NOT support `@ServiceConnection` or `spring-boot-testcontainers`.** Use `@DynamicPropertySource` to inject the emulator connection string into the Spring context. - -```java -@SpringBootTest -@ActiveProfiles("test") // MANDATORY -@Testcontainers -@Tag("Layer1") -class MessageServiceL1Test { - - static final Network NETWORK = Network.newNetwork(); - - @Container - static final MSSQLServerContainer SQL_SERVER = new MSSQLServerContainer<>( - "mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04") - .acceptLicense() - .withPassword("yourStrong(!)Password") - .withNetwork(NETWORK) - .withNetworkAliases("mssql"); - - @Container - static final GenericContainer SERVICE_BUS = new GenericContainer<>( - "mcr.microsoft.com/azure-messaging/servicebus-emulator:1.1.2") - .withExposedPorts(5672) - .withNetwork(NETWORK) - .withEnv("ACCEPT_EULA", "Y") - .withEnv("MSSQL_SA_PASSWORD", "yourStrong(!)Password") - .withEnv("SQL_SERVER", "mssql") - .withCopyFileToContainer( - MountableFile.forClasspathResource("config.json"), - "/ServiceBus_Emulator/ConfigFiles/config.json") - .dependsOn(SQL_SERVER) - .waitingFor(Wait.forLogMessage(".*Emulator Service is Successfully Up!.*", 1) - .withStartupTimeout(Duration.ofMinutes(3))); - - // NO @MockBean — all beans wired from the real emulator - @DynamicPropertySource - static void serviceBusProperties(DynamicPropertyRegistry registry) { - String connectionString = String.format( - "Endpoint=sb://%s:%d;SharedAccessKeyName=RootManageSharedAccessKey;" - + "SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;", - SERVICE_BUS.getHost(), SERVICE_BUS.getMappedPort(5672)); - registry.add("spring.cloud.azure.servicebus.connection-string", () -> connectionString); - registry.add("spring.cloud.azure.servicebus.namespace", () -> "sbemulatorns"); - // Disable managed identity — use the emulator connection string instead - registry.add("spring.cloud.azure.credential.managed-identity-enabled", () -> "false"); - } - - @Autowired - private MessageService messageService; // The app's own service class - - @Test - void sendMessageThroughApplicationService() { - // ALWAYS test through the application's own classes, not the SDK directly - Awaitility.await() - .atMost(Duration.ofSeconds(120)) - .pollInterval(Duration.ofSeconds(2)) - .untilAsserted(() -> { - messageService.sendMessage("test-queue", "Hello from test!"); - }); - } -} -``` - -## Handle ServiceBusAdministrationClient - -**CRITICAL: The Service Bus emulator does NOT support the ServiceBusAdministrationClient REST API.** The emulator only exposes AMQP on port 5672 for messaging operations. The `ServiceBusAdministrationClient` uses HTTPS REST API (port 443) which the emulator does not provide. Any call to `adminClient.getTopic()`, `adminClient.createSubscription()`, etc. will fail with `Connection refused: localhost:443`. - -### When the Application Uses ServiceBusAdministrationClient - -If the application has beans that use `ServiceBusAdministrationClient`, you MUST: -1. Use a test configuration that doesn't include the admin client. -2. **Include ALL dependent beans** - if `TopicProperties mainExchangeTopic` depends on `adminClient`, you must exclude BOTH -3. **Pre-create topics/queues/subscriptions in config.json** instead of relying on admin API calls - -### Complete Test Configuration Example - -When the application has code like this: - -```java -// Production code -@Bean -public ServiceBusAdministrationClient adminClient(AzureServiceBusProperties properties, TokenCredential credential) { - return new ServiceBusAdministrationClientBuilder() - .credential(properties.getFullyQualifiedNamespace(), credential) - .buildClient(); -} - -@Bean -public TopicProperties mainExchangeTopic(ServiceBusAdministrationClient adminClient) { - try { - return adminClient.getTopic(MAIN_EXCHANGE); // ← This FAILS against emulator! - } catch (ResourceNotFoundException e) { - return adminClient.createTopic(MAIN_EXCHANGE); - } -} -``` -**You MUST Exclude Admin Beans with @Profile:** - -```java -// Production code - mark with profile exclusion -@Bean -@Profile("!test") // Excluded in test profile -public ServiceBusAdministrationClient adminClient(AzureServiceBusProperties properties, TokenCredential credential) { - // ... -} - -@Bean -@Profile("!test") // Excluded in test profile -public TopicProperties mainExchangeTopic(ServiceBusAdministrationClient adminClient) { - // ... -} -``` - -**IMPORTANT:** Pre-create the topic in your `service-bus-config.json`: - -```json -{ - "UserConfig": { - "Namespaces": [{ - "Name": "sbemulatorns", - "Topics": [{ - "Name": "mainExchange", - "Subscriptions": [/* pre-create any needed subscriptions */] - }] - }] - } -} -``` - -## Emulator Readiness and Retry - -The Service Bus emulator takes significantly longer to start than Azurite because it depends on SQL Server initializing messaging entities. **Always use Awaitility retry/polling for initial send operations — never `Thread.sleep()`.** - -```java -// Typical total time from container start to first successful send: 60-120 seconds -waitAtMost(Duration.ofSeconds(120)) - .pollInterval(Duration.ofSeconds(2)) - .untilAsserted(() -> { - senderClient.sendMessage(new ServiceBusMessage("test")); - }); -``` - -## Emulator fails to start -If the emulator won't start (Docker not available, timeout, resource limits), **do NOT fall back to mocking.** Instead: -1. Increase the startup timeout (the emulator needs 60-120s) -2. Check Docker resources (emulator + SQL Server need ~4GB RAM) -3. Verify config.json is correctly mounted -4. Check the emulator logs for specific errors - -## Common Gotchas - -| Problem | Cause | Fix | -|---------|-------|-----| -| `ClassNotFoundException: ServiceBusEmulatorContainer` | testcontainers version < 1.20.5 | Upgrade to >= 1.20.5 OR use GenericContainer approach | -| `cannot find symbol: @ServiceConnection` | Spring Boot < 3.1 | Use `@DynamicPropertySource` instead (see Spring Boot 2.x pattern) | -| `UnsatisfiedDependencyException: TokenCredential` | Custom `@Configuration` creates Azure beans without correct emulator properties | Add `@ActiveProfiles("test")` AND provide emulator connection string via `@DynamicPropertySource` so all beans connect to the real emulator | -| `ServiceBusException: Entity not found` | Emulator entities not yet initialized | Use `waitAtMost` retry pattern with Awaitility | -| `Connection refused on port 5672` | SQL Server not ready when emulator starts | Use `dependsOn(SQL_SERVER)` + `waitingFor` log message strategy | -| `config.json not found` | Wrong mount path | Ensure `MountableFile.forClasspathResource("config.json")` matches file in `src/test/resources/` | -| Queue/topic name mismatch | config.json names don't match test properties | Verify `entity-name` matches config.json `Name` field | -| `No qualifying bean of type ServiceBusSenderClient` | Missing auto-configuration imports | Add `@ImportAutoConfiguration` with `AzureGlobalPropertiesAutoConfiguration`, `AzureServiceBusAutoConfiguration`, `AzureServiceBusMessagingAutoConfiguration` | -| `Checkpointer is null` | `auto-complete` not disabled | Set `spring.cloud.stream.servicebus.bindings.-in-0.consumer.auto-complete=false` | -| `InvalidDestinationException` on context start | Test context loads production JMS `@Configuration` that connects to fake endpoint | Add `@ActiveProfiles("test")` and provide correct emulator connection properties via `@DynamicPropertySource` | -| Tests hang during context startup | `@SpringBootTest` loads full context with real Azure/DB connections | Add `@ActiveProfiles("test")` and provide all emulator properties via `@DynamicPropertySource`, or narrow context with `classes = {...}` | diff --git a/.github/skills/integration-tests/references/azure-storage-testcontainers.md b/.github/skills/integration-tests/references/azure-storage-testcontainers.md deleted file mode 100644 index 4449ca75c..000000000 --- a/.github/skills/integration-tests/references/azure-storage-testcontainers.md +++ /dev/null @@ -1,276 +0,0 @@ -# Azure Blob Storage with Azurite TestContainers Coding Reference - -## Contents -- 1.Azurite-Specific Self-Checks -- 2.Azurite Container Setup - + Azurite well-known credentials -- 3.Azurite URL Format Gotcha -- 4.Spring Boot Config Override for Blob Storage - + ⚠️ CRITICAL: `System.getenv()` vs Spring Properties Mismatch -- 5. Shared Azurite Base Class -- 6. Azure Blob-Specific Test Patterns - + Testing Application Code (Not the SDK) - + Asserting on Behavior, Not Structure - + Error Handling with BlobStorageException - -## 1.Azurite-Specific Self-Checks -Before running tests, verify: -- [ ] If any application code reads config via `System.getenv()`, you've provided values through constructor injection or `@TestConfiguration` beans — NOT through `@DynamicPropertySource` (which only sets Spring properties) -- [ ] If the application parses URLs to extract container names, you've handled the Azurite URL format difference (see section 2) -- [ ] No test uses `DefaultAzureCredentialBuilder` — use `StorageSharedKeyCredential` with Azurite dev credentials -- [ ] No test checks for `blob.core.windows.net` in URLs — Azurite URLs use `localhost` -- [ ] `@ActiveProfiles("test")` is on every `@SpringBootTest` class (if using Spring Boot) -- [ ] `@BeforeEach` cleanup deletes all blob containers to prevent cross-test contamination - -## 2.Azurite Container Setup -```java -new GenericContainer<>("mcr.microsoft.com/azure-storage/azurite:latest") - .withExposedPorts(10000) - .withCommand("azurite-blob", "--blobHost", "0.0.0.0") - .waitingFor(Wait.forListeningPort()); // REQUIRED — prevents race conditions -``` - -### Azurite well-known credentials (safe for local testing only) -- Account name: `devstoreaccount1` -- Account key: `Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==` - -## 3.Azurite URL Format Gotcha -**IMPORTANT — Azurite URL format differs from production Azure Blob Storage:** - -- **Production Azure:** `https://.blob.core.windows.net//` -- **Azurite:** `http://:/devstoreaccount1//` - -If the application code parses Azure Blob Storage URLs (e.g., extracting container name from the URL path), the Azurite URL will have an extra `/devstoreaccount1/` path segment that production URLs don't have. This causes: -- Container name extracted as `devstoreaccount1` instead of the actual container name -- URL-based detection logic (e.g., checking for `blob.core.windows.net`) failing for Azurite URLs -- Path indices being off-by-one when splitting URLs into segments - -**How to handle this — choose the approach that requires the LEAST production code change:** - -1. **Best approach — Inject the container name and BlobServiceClient separately** instead of having the application parse them from a URL: - ```java - // Add a test-friendly constructor that accepts pre-built client + container name - // Minimal source change: 3-4 lines - S3FileSystemStore(String rootPath, BlobServiceClient client, String containerName) { - this.blobServiceClient = client; - this.containerName = containerName; - this.rootPath = rootPath; - } - ``` - -2. **If the application detects Azure by checking for `blob.core.windows.net`** in the URL (common pattern), provide an override mechanism so tests can force Azure mode: - ```java - // Add a boolean flag or config property to bypass URL detection - S3FileSystemStore(String rootPath, FileSystem fs, BlobServiceClient client, boolean isAzure) { - this.blobServiceClient = client; - this.isAzureMode = isAzure; // skip blob.core.windows.net check - } - ``` -3. **If the application splits the URL path to extract container names**, the path indices will be different for Azurite vs Azure: - - Azure URL path: `//` -> `pathParts[1]` = container name - - Azurite URL path: `/devstoreaccount1//` -> `pathParts[1]` = `devstoreaccount1`, `pathParts[2]` = container name - - **Never assume Azurite URLs have the same path structure as production Azure URLs.** Instead, pass the container name as a parameter to avoid URL parsing entirely. - -**Self-check:** Before running tests, ask "Does any application code parse URLs to extract container names or detect Azure mode?" If yes, you MUST handle the Azurite URL format difference or 60%+ of tests will fail at runtime. - -## 4.Spring Boot Config Override for Blob Storage - -Production code often uses `DefaultAzureCredentialBuilder` which requires HTTPS and real Azure endpoints. Tests use local Azurite over HTTP with well-known keys. You **MUST** override the production bean configuration so the test context starts successfully. - -**Step 1 — ALWAYS add `@ActiveProfiles("test")` to every `@SpringBootTest` class:** -```java -@SpringBootTest -@ActiveProfiles("test") // MANDATORY — prevents production @Profile("!test") beans from loading -@Testcontainers -class MyServiceL1Test { ... } -``` -Without `@ActiveProfiles("test")`, production configuration classes (DataSource configs, credential builders, startup initializers) will load and fail because they try to connect to real services. This is the **#1 cause of Spring context startup failures** in integration tests. - -**Step 2 — use `@TestConfiguration` with `@Primary` to override production beans:** - -```java -@TestConfiguration -public class TestBlobStorageConfig { - // Override the production BlobServiceClient that uses DefaultAzureCredential - @Bean - @Primary - public BlobServiceClient blobServiceClient( - @Value("${azure.storage.endpoint}") String endpoint) { - // Azurite well-known credentials — safe for local testing only - StorageSharedKeyCredential credential = new StorageSharedKeyCredential( - "devstoreaccount1", - "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="); - return new BlobServiceClientBuilder() - .endpoint(endpoint) - .credential(credential) - .buildClient(); - } -} -``` - -**Step 3 — narrow the Spring context when full context is too heavy:** -If `@SpringBootTest` loads too many unrelated beans (startup initializers, schedulers, external integrations) that fail during context startup, use context slicing: -```java -@SpringBootTest(classes = {MyService.class, TestBlobStorageConfig.class}) -``` -This loads only the beans you need for testing, avoiding failures from unrelated components. - -### ⚠️ CRITICAL: `System.getenv()` vs Spring Properties Mismatch - -**`@DynamicPropertySource` sets Spring properties — it does NOT set environment variables.** If the application's DAO/service reads configuration via `System.getenv("AZURE_STORAGE_ENDPOINT")`, setting `registry.add("AZURE_STORAGE_ENDPOINT", ...)` in `@DynamicPropertySource` will NOT work. The DAO will receive `null` and fail with `NullPointerException` before any test runs. - -**How to detect this:** Before writing any test, `grep -r "System.getenv\|System.getProperty" src/main/` to find all environment variable reads in the application code. If you find any, you MUST use one of the approaches below. - -**Fix — choose the approach that requires the LEAST production code change:** - -1. **Best approach — Add a constructor that accepts the dependencies directly** (minimal safe source change, 3-5 lines): - ```java - // Add to the production DAO class: - AzureBlobDigitalMediaDAO(BlobServiceClient client, String containerName) { - this.blobServiceClient = client; - this.containerName = containerName; - } - ``` - Then in the test, provide the client via `@TestConfiguration` with `@Primary`: - ```java - @TestConfiguration - static class TestConfig { - @Bean @Primary - public AzureBlobDigitalMediaDAO testDao() { - return new AzureBlobDigitalMediaDAO(blobServiceClient, CONTAINER_NAME); - } - } - ``` - -2. **Alternative — Use `@TestConfiguration` `@Primary` bean + `@ConditionalOnMissingBean`** on the production config class so the test bean takes precedence. - -3. **Last resort — Set actual environment variables via `System.setProperty()` paired with changing production code from `System.getenv()` to `System.getProperty()`** (slightly larger source change but avoids constructor changes). - -**Anti-pattern — DO NOT DO THIS:** -```java -// WRONG: @DynamicPropertySource does NOT populate System.getenv() -@DynamicPropertySource -static void props(DynamicPropertyRegistry registry) { - registry.add("AZURE_STORAGE_ENDPOINT", () -> azuriteEndpoint); // ❌ DAO calls System.getenv(), not Spring -} -``` - -**Self-check:** For every `System.getenv()` call in the production code, verify your test provides the value through a mechanism the production code actually reads. `@DynamicPropertySource` only works for code that reads from Spring's `Environment` (e.g., `@Value`, `@ConfigurationProperties`). - -**Key rules:** -- ALWAYS add `@ActiveProfiles("test")` to every `@SpringBootTest` test class — this is the most common mistake. -- NEVER use `DefaultAzureCredentialBuilder` in tests — it will fail on HTTP endpoints and in CI. -- ALWAYS use `StorageSharedKeyCredential` with Azurite's well-known dev credentials for Blob Storage tests. -- Provide test properties via `@DynamicPropertySource` from the TestContainers container (e.g., `azuriteContainer.getHost()` + `azuriteContainer.getMappedPort(10000)`). -- If the full `@SpringBootTest` context fails to start, narrow it with `classes = {...}` rather than trying to mock/stub all failing beans. - -## 5.Shared Azurite Base Class - -When you have 2+ test classes using Azurite, extract the container declaration and client construction into a shared abstract base class: - -```java -// Shared base class — declared once, reused by all test classes -@Testcontainers -abstract class AbstractAzuriteL1Test { - @Container - static final GenericContainer azurite = new GenericContainer<>( - "mcr.microsoft.com/azure-storage/azurite:latest") - .withExposedPorts(10000) - .withCommand("azurite-blob", "--blobHost", "0.0.0.0"); - - protected static BlobServiceClient blobServiceClient; - - @BeforeAll - static void initClient() { - String endpoint = String.format("http://%s:%d/devstoreaccount1", - azurite.getHost(), azurite.getMappedPort(10000)); - blobServiceClient = new BlobServiceClientBuilder() - .connectionString(getAzuriteConnectionString()) - .buildClient(); - } -} - -// Each test class extends the base — no duplicated container setup -@Tag("Layer1") -class EventHandlerL1Test extends AbstractAzuriteL1Test { ... } -class S3ClientUtilL1Test extends AbstractAzuriteL1Test { ... } -``` - -## 6.Azure Blob-Specific Test Patterns - -### Testing Application Code (Not the SDK) - -**Anti-pattern — DO NOT DO THIS:** -```java -// WRONG: Test calls Azure SDK directly instead of the application's CreateBucket class -@Test void testCreateBucketClass() { - // This tests the Azure SDK, NOT the application code - blobServiceClient.createBlobContainer("test-container"); - assertTrue(blobServiceClient.getBlobContainerClient("test-container").exists()); -} -``` - -**Anti-pattern — DO NOT DO THIS either:** -```java -// WRONG: Tests individual Azure Blob operations instead of the application's handler -@Test void testUploadBlob() { - BlobContainerClient container = blobServiceClient.getBlobContainerClient("test"); - container.create(); - container.getBlobClient("file.txt").upload(BinaryData.fromString("data"), true); - String content = container.getBlobClient("file.txt").downloadContent().toString(); - assertEquals("data", content); - // This verifies Azure SDK works — it does NOT test the application's EventHandler -} -``` - -**Correct pattern — invoke the application's own entry points:** -```java -// RIGHT: Test calls the application's own handler method end-to-end -@Test void testHandleRequestProcessesEventsEndToEnd() { - // Arrange — pre-populate Azurite with test event data - uploadTestEvent("container1", "event1.txt", "status:SHIPPED\ntimestamp:1573410202"); - - // Act — invoke the APPLICATION's handler, not the SDK - EventHandler handler = new EventHandler(blobServiceClient); - handler.handleRequest(mockContext); - - // Assert — verify the application produced the expected output - String summary = downloadBlob("summary-container", "summary.txt"); - assertThat(summary).contains("SHIPPED"); -} -``` - -### Asserting on Behavior, Not Structure - -**Every write test must read the data back and verify content.** If you write a blob to Azurite, download it and assert the content matches — do NOT just assert that the container exists (which is always true if you created it in `@BeforeEach`). A test whose only assertion is `assertTrue(container.exists())` or `assertNotNull(result)` is effectively testing nothing. - -### Error Handling with BlobStorageException - -```java -// RIGHT: Test error handling through the application class -@Test void testCreateBucketWithInvalidName() { - RuntimeException ex = assertThrows(RuntimeException.class, - () -> createBucket.createBucketAsync("INVALID-UPPERCASE")); - assertThat(ex.getMessage()).contains("Failed to create bucket"); - assertThat(ex.getCause()).isInstanceOf(BlobStorageException.class); -} -``` - -For each `catch (BlobStorageException e)` block in the migrated code, write a corresponding test: -```java -// If the source code has: -try { - blobClient.upload(data, true); -} catch (BlobStorageException e) { - throw new RuntimeException("Failed to create bucket", e); -} - -// Then you MUST have this test: -@Test void testCreateBucketWrapsStorageException() { - RuntimeException ex = assertThrows(RuntimeException.class, - () -> createBucket.createBucketAsync("INVALID")); - assertThat(ex.getMessage()).contains("Failed to create bucket"); - assertThat(ex.getCause()).isInstanceOf(BlobStorageException.class); -} -``` diff --git a/.github/skills/integration-tests/references/layer1-local-integration.md b/.github/skills/integration-tests/references/layer1-local-integration.md deleted file mode 100644 index 2b2d06262..000000000 --- a/.github/skills/integration-tests/references/layer1-local-integration.md +++ /dev/null @@ -1,358 +0,0 @@ -# Layer 1: Local Integration Tests - -## Contents -- Prerequisites (Docker & TestContainers available) -- Azure Authentication Strategies for Integration Tests Layer 1 (TokenCredential,Connection Strings, Keys) - + Emulatable Azure Services - + Emulator Connection String Values - + Modification Rules -- Principles - + 1. Core Principles (naming convention, no source code modification to fix TestContainers or Docker compatibility issues) - + 2. MOST IMPORTANT: Test application code, not the SDK - + 3. Make untestable code testable with minimal, safe source changes - + 4. Wire and test the full execution path - + 5. Assert on behavior, not structure - + 6. Only set up what the test uses - + 7. Test edge cases - + 8. Cover downstream consumers - + 9. Only label tests as integration tests if they integrate external systems - + 10. Stay scoped to the migration target - + 11. DO NOT use any test ordering mechanism - + 12. DO NOT use reflection to access or modify private fields,DO NOT widen field visibility - + 13. DO write specific, descriptive assertions - + 14. DO structure every test with clear Arrange-Act-Assert (AAA) sections - + 15. DO add `@BeforeEach` cleanup when tests share a container - + 16. DO verify the test count after `mvn verify` matches expectations - + 17. DO extract shared test infrastructure into a base class or helper when you have 2+ test classes - + 18. DO extract commonly repeated test patterns into helper methods - + 19. DO NOT leave `System.out.println` or debug logging statements in test code - + 20. NEVER use `Thread.sleep()` to wait for containers to start - -## Prerequisites - -- Docker installed and running -- TestContainers library available for the project's language - -## Azure Authentication Strategies for Integration Tests Layer 1 - -Migrated applications typically use **Microsoft Entra ID OAuth** such as **Managed Identity** (`DefaultAzureCredential`) which only works on Azure infrastructure. For local integration testing applications must connect to local emulators. - -### Emulatable Azure Services - -These services have official emulators that can run in Docker containers: - -| Azure Service | Emulator Image | Dependencies | -|---------------|----------------|--------------| -| Blob / Queue / Table Storage | `mcr.microsoft.com/azure-storage/azurite` | None | -| Cosmos DB | `mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator` | None (but requires SSL trust store + endpoint/key auth) | -| SQL Database | `mcr.microsoft.com/mssql/server` | None (wire-compatible with SQL Server) | -| Service Bus | `mcr.microsoft.com/azure-messaging/servicebus-emulator` | Requires MSSQL companion + JSON config | -| Event Hubs | `mcr.microsoft.com/azure-messaging/eventhubs-emulator` | Requires Azurite companion + JSON config | - -> **Note:** Service Bus and Event Hubs emulators require companion containers and a JSON config file. See [azure-servicebus-testcontainers.md](references/azure-servicebus-testcontainers.md) for the config format. - -**Cosmos DB emulator note:** Unlike other emulators which use connection strings, the Cosmos DB emulator authenticates via **endpoint + account key** and requires an **SSL certificate** imported into a trust store (e.g., `javax.net.ssl.trustStore` for Java). If SSL setup is too complex, treat Cosmos DB as non-emulatable and recommend Layer 3. - -### Authentication Modification Strategies (TokenCredential,Connection Strings, Keys) - -For each emulatable Azure dependency, modify how the application authenticates: - -#### Config-Driven Applications - -**Examples:** Spring Boot applications with `application.properties` or `application.yml`, Quarkus/Micronaut applications with `application.properties`. - -**Strategy:** Inject the connection string via test configurations, like property source. - -**CRITICAL:** You MUST always modify the config file to add the explicit connection string value. Do NOT rely on environment variables. - -#### Hardcoded/Plain Applications - -**Examples:** Plain Java CLI apps without config frameworks - -**When to use:** Applications that have NO configuration framework (no Spring Boot, no application.properties). These apps construct clients directly in code with hardcoded values. - -**Strategy:** Modify the source code to extend the client constructor which accepts hardcode emulator connection strings. Since these applications don't have config files to modify, the connection string must be in the source code itself to be committed to git. - -**Java example:** - -**Before:** -```java -public class BlobServiceClientProvider { - private static BlobServiceClient blobServiceClient; - - public static BlobServiceClient getClient() { - if (blobServiceClient == null) { - String endpoint = System.getenv("AZURE_STORAGE_ENDPOINT"); - blobServiceClient = new BlobServiceClientBuilder() - .endpoint(endpoint) - .credential(new DefaultAzureCredentialBuilder().build()) - .buildClient(); - } - return blobServiceClient; - } -} -``` - -**After:** -```java -public class BlobServiceClientProvider { - private static BlobServiceClient blobServiceClient; - - public static BlobServiceClient getClient() { - if (blobServiceClient == null) { - String endpoint = System.getenv("AZURE_STORAGE_ENDPOINT"); - blobServiceClient = new BlobServiceClientBuilder() - .endpoint(endpoint) - .credential(new DefaultAzureCredentialBuilder().build()) - .buildClient(); - } - return blobServiceClient; - } - - public static BlobServiceClient getClient(String connectionString) { - if (blobServiceClient == null) { - blobServiceClient = new BlobServiceClientBuilder() - .connectionString(testConnectionString) - .buildClient(); - } - return blobServiceClient; - } -} -``` - -### Emulator Connection String Values - -Each emulator's connection string is built from **well-known credentials** that are consistent across all emulator instances. - -| Service | Auth Type | How to Determine the Value | -|---------|-----------|----------------------------| -| Azurite (Blob/Queue/Table) | Connection string | Built from well-known account `devstoreaccount1`, well-known key, and mapped ports (10000/10001/10002). See [Azurite docs](https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite#well-known-storage-account-and-key). | -| Service Bus | Connection string | Built from well-known SAS key + `UseDevelopmentEmulator=true`. See [Service Bus emulator docs](https://learn.microsoft.com/en-us/azure/service-bus-messaging/test-locally-with-service-bus-emulator). | -| Event Hubs | Connection string | Same pattern as Service Bus. See [Event Hubs emulator docs](https://learn.microsoft.com/en-us/azure/event-hubs/test-locally-with-event-hub-emulator). | -| Cosmos DB | Endpoint + key | Built from well-known emulator key + mapped port. See [Cosmos emulator docs](https://learn.microsoft.com/en-us/azure/cosmos-db/emulator). Also requires SSL trust store setup. | - -**Important:** The agent must look up the **actual current default credentials** from the linked official docs at generation time. Emulator defaults may change across versions. Do NOT hardcode connection strings from this file. - -### Modification Rules - -When modifying application code or config to use emulators: - -1. **Minimal changes only** — Only modify what's needed to switch auth from Managed Identity to emulator connection strings. Do not refactor, rename, or restructure. -2. **No new dependencies** — Use only packages already in the project. -3. **Reversible** — Changes should be easily revertable to restore original Managed Identity auth. -4. **No unrelated changes** — Never make refactoring, cleanup, or feature changes alongside auth modifications. - -## Principles - -### Core Principles - -1. **DO integration tests** to verify that different components interact correctly, focusing on data exchange and interface connections. -2. **DO write** comprehensive integration tests using containers to simulate all migrated dependencies, **especially Azure services**. -6. **NEVER modify application source code** to fix TestContainers or Docker compatibility issues. -8. **DO use `*L1Test.java`** as the class name suffix for Layer 1 test classes. This matches Maven Surefire's default `*Test.java` pattern - -### MOST IMPORTANT: Test application code, not the SDK - -Always instantiate and invoke the project's own classes/methods. Never call third-party library APIs directly in the test as a substitute for testing the application. If the application has a service class with methods like `handleRequest()`, `processData()`, or `createBucket()`, your tests MUST call those methods. - **Anti-pattern — DO NOT DO THIS:** - ```java - // WRONG: Test calls SDK directly instead of the application's own class - @Test void testServiceMethod() { - // This tests the SDK, NOT the application code - sdkClient.doOperation("test-input"); - assertTrue(sdkClient.getResult("test-input").exists()); - } - ``` - - **Correct pattern — invoke the application's own entry points:** - ```java - // RIGHT: Test calls the application's own handler method end-to-end - @Test void testHandleRequestProcessesDataEndToEnd() { - // Arrange — pre-populate test data - setupTestData("container1", "data1.txt", "status:ACTIVE\ntimestamp:1573410202"); - - // Act — invoke the APPLICATION's handler, not the SDK - MyHandler handler = new MyHandler(sdkClient); - handler.handleRequest(mockContext); - - // Assert — verify the application produced the expected output - String result = readOutput("output-container", "result.txt"); - assertThat(result).contains("ACTIVE"); - } - ``` - **Never bypass the application class by re-implementing its logic with direct SDK calls.** - - > For Azure Blob Storage-specific examples (Azurite anti-patterns, `BlobServiceClient` injection, `EventHandler` testing), see [azure-storage-testcontainers.md](./azure-storage-testcontainers.md). - -### Make untestable code testable with minimal, safe source changes - -If production code cannot be invoked from a test, introduce the smallest possible change (e.g., adding a method, widening visibility, extracting a parameter) to enable it. Never alter existing behavior, signatures, or control flow. Never work around untestable code by re-implementing its logic in the test. - -### Wire and test the full execution path - -Tests must exercise the end-to-end flow from input through business logic to output, not individual layers (controller layer or database layer) in isolation. - -### Assert on behavior, not structure - -Verify correct values, side effects, and state transitions — not just that a result is non-null or non-empty, nor rely on string containment or format checks alone. **Every write test must read the data back and verify content.** A test whose only assertion is `assertTrue(result.exists())` or `assertNotNull(result)` is effectively testing nothing. - -### Only set up what the test uses - -Every configured property, container, or singleton in setUp must be exercised by the test. Remove dead setup. - -### Test error handling at the application level with precise assertions — this is MANDATORY, not optional. - - **Anti-pattern — DO NOT DO THIS:** - ```java - // WRONG: Only testing happy paths - @Test void testOperation() { sdkClient.doOperation("test"); /* only happy path */ } - ``` - - **Correct pattern:** - ```java - // RIGHT: Test error handling through the application class - @Test void testOperationWithInvalidInput() { - RuntimeException ex = assertThrows(RuntimeException.class, - () -> myService.processInput("INVALID-INPUT")); - assertThat(ex.getMessage()).contains("Failed to process"); - assertThat(ex.getCause()).isInstanceOf(SomeSDKException.class); - } - ``` - - **Correct pattern for JSON deserialization error (common with Redis/cache migrations):** - ```java - // RIGHT: Inject corrupted data directly into the store, then test the app's error path - @Test void testGetPersonWithCorruptedJson() { - // Arrange — write invalid JSON directly to Redis in the SAME namespace the app uses - redisCommands.set("personCache:badKey", "{not-valid-json!!}"); - - // Act & Assert — the app's getPerson() should catch JsonProcessingException and wrap it - RuntimeException ex = assertThrows(RuntimeException.class, - () -> cacheManager.getPerson("badKey")); - assertThat(ex.getMessage()).contains("Failed to deserialize"); - assertThat(ex.getCause()).isInstanceOf(JsonProcessingException.class); - } - ``` - **Note:** When testing deserialization errors, write corrupted data to the **same key namespace/prefix** that the application reads from. A common mistake is writing to namespace `stringCache:key` but reading from `personCache:key` — this just produces a cache miss (null), not a deserialization error. - - > For Azure Blob Storage-specific error handling patterns (e.g., `BlobStorageException` wrapping), see [azure-storage-testcontainers.md](./azure-storage-testcontainers.md#5-azure-blob-specific-test-patterns). - - #### Migration-Critical Error Scenarios Checklist - - The following error scenarios are the most commonly missed in migration integration tests. **You MUST check each row and write a test for every scenario that applies to your migration.** Skipping these is the #1 cause of low error handling scores. - - | # | Scenario | When it applies | What to test | Example assertion | - |---|----------|----------------|--------------|-------------------| - | 1 | **SDK exception wrapping** | App has `catch (SDKException e) { throw new ...}` | Trigger the SDK exception through the app's own method and verify the wrapper exception type, message, AND cause chain | `assertThat(ex.getCause()).isInstanceOf(SDKException.class)` | - | 2 | **Resource-not-found via app method** | App reads blobs, DB rows, cache keys that may not exist | Call the app's read method with a non-existent resource; assert on the specific return (null, empty Optional, 404) — not just "no exception" | `assertThat(dao.load(99999, "x")).isNull()` | - | 3 | **Inconsistent state between stores** | App uses 2+ stores (DB + blob, DB + cache) | Write metadata to one store but NOT the other, then call the app's read method — verify it handles the mismatch (exception, null, graceful fallback) | Insert DB row, don't upload blob -> `dao.load()` should throw/return-null | - | 4 | **Invalid/null input through app methods** | App has public methods accepting user-provided strings, IDs, names | Pass null, empty string, and boundary-length values through the app's own method; assert specific exception type or error response | `assertThrows(IllegalArgumentException.class, () -> svc.create(null))` | - | 5 | **Duplicate/conflict operations** | App creates named resources (containers, keys, DB records) | Create a resource, then try to create it again through the app method; verify the specific conflict behavior (exception type, idempotent success, or error code) | `assertThat(ex.getMessage()).contains("already exists")` | - | 6 | **Delete non-existent resource** | App has delete/remove methods | Call delete on a resource that doesn't exist; verify whether it throws, returns false, or is idempotent — **this often differs between AWS SDK and Azure SDK** | `assertThat(svc.delete("nonexistent")).isFalse()` | - | 7 | **Operations after close/shutdown** | App has lifecycle methods (close, shutdown, stopService) | Call close(), then attempt a normal operation; verify it throws the expected exception (NPE, IllegalStateException, etc.) | `svc.close(); assertThrows(IllegalStateException.class, () -> svc.read("key"))` | - | 8 | **Corrupted/invalid data in store** | App deserializes data from external store (JSON from Redis, parsed blob content) | Write corrupted/malformed data directly to the store, then call the app's read method; verify the deserialization error is properly handled | `redis.set("key", "{bad-json}"); assertThrows(RuntimeException.class, () -> mgr.get("key"))` | - - **Self-check:** Count your error test methods. If you have fewer than 3 error tests from the table above, you almost certainly have gaps. Go back and add more. - -### Cover downstream consumers - -If the migrated code produces output consumed by other components (config generators, report builders), test those consumers too. Verify output is consumable by the next component in the pipeline — write then read back through the real consumer API to confirm round-trip correctness. - -### Only label tests as integration tests if they integrate external systems - -Tests with no containers, network, or filesystem dependencies are unit tests, which is not within the IT scope. Do not generate them alongside integration tests. - -### Stay scoped to the migration target - -Only generate tests for code that uses the migrated service. Do not generate unrelated tests for general project utilities. - -### DO NOT use any test ordering mechanism — this is an AUTOMATIC QUALITY DEDUCTION - -This includes JUnit 5 `@Order` / `@TestMethodOrder`, TestNG `dependsOnMethods` / `priority`, and any other framework-specific ordering. Tests MUST be independent and run in any order. Each test must create its own data and clean up after itself. Test ordering indicates shared mutable state — a critical quality issue. Evaluators specifically check for `@Order` and `@TestMethodOrder` annotations and will deduct points for their presence. - - **Anti-pattern — DO NOT DO THIS:** - ```java - // WRONG: @Order creates test interdependence — automatic quality deduction - @TestMethodOrder(MethodOrderer.OrderAnnotation.class) - class MyL1Test { - @Test @Order(1) void testCreate() { ... } - @Test @Order(2) void testRead() { ... } // implicitly depends on testCreate - } - ``` - - **Correct pattern:** - ```java - // RIGHT: Each test is self-contained with its own setup - class MyL1Test { - @BeforeEach void cleanup() { /* clean all state */ } - @Test void testCreate() { /* creates its own data */ } - @Test void testRead() { /* creates its own data, then reads */ } - } - ``` -### DO NOT use reflection to access or modify private fields, and DO NOT widen field visibility (private->protected/public) in production code for test access. - -Both approaches are fragile and tightly couple tests to implementation details. Reflection breaks on Java 17+ with strong encapsulation. Widening visibility pollutes the production API and can cause compilation errors when test classes are in a different package (protected access requires same package or subclass). Instead: - - For singletons: add a package-private or test-visible constructor/factory method to the production class (minimal safe change). - - For environment variables: use `@DynamicPropertySource`, `System.setProperty()` with matching application code reads, constructor/setter injection, or `@TestConfiguration` beans. - - For configuration files: use `@TestPropertySource`, classpath-based overrides in `src/test/resources/`, or `@DynamicPropertySource`. - - For injecting test dependencies: use constructor injection, setter injection, or `@TestConfiguration` with `@Primary` beans. - - For inspecting internal state: add a package-private getter method or test the observable behavior (outputs, side effects) instead of reading internal fields. - -### DO write specific, descriptive assertions - - ```java - // WRONG — poor failure message: "expected: true but was: false" - assertTrue(containers.contains("my-container")); - assertTrue(ex.getCause() instanceof SomeException); - - // RIGHT — clear failure message showing actual values - assertThat(containers).contains("my-container"); - assertThat(ex.getCause()).isInstanceOf(SomeException.class); - assertThat(result.getName()).isEqualTo("expected-name"); // shows both values on failure - ``` - - Regardless of library, always assert on specific values — not just `assertNotNull(result)` or `assertTrue(success)`. Include `assertInstanceOf()` (JUnit 5.8+) instead of `assertTrue(x instanceof Y)` for better error messages. - -### DO structure every test with clear Arrange-Act-Assert (AAA) sections - separated by blank lines. Each test should verify ONE logical behavior. Do not combine multiple unrelated operations in a single test method. - -### DO add `@BeforeEach` cleanup when tests share a container - -If multiple tests use the same TestContainers instance (e.g., a shared Redis or Azurite container), each test must clean up its state in `@BeforeEach` or `@AfterEach` (e.g., flush Redis, delete all blob containers, truncate database tables). Stale data from one test must never affect another. Without cleanup, test execution order determines results — a critical reliability issue. - -### DO verify the test count after `mvn verify` matches expectations - -Before committing, run `mvn verify` and confirm the number of tests discovered and executed matches the number of test methods you wrote. If the count is lower, some tests are not being discovered — fix the build configuration. - -### DO extract shared test infrastructure into a base class or helper when you have 2+ test classes - -If multiple test classes use the same TestContainers setup (e.g., same Azurite/Redis/PostgreSQL container), extract the container declaration, connection string construction, and `@DynamicPropertySource` into a shared abstract base class (e.g., `AbstractAzuriteL1Test` or `AbstractRedisL1Test`). This eliminates duplication and ensures consistent infrastructure configuration. Each test class that needs the container simply extends the base class. - - > For an Azurite-specific shared base class example (`AbstractAzuriteL1Test`), see [azure-storage-testcontainers.md](./azure-storage-testcontainers.md#4-shared-azurite-base-class). - -### DO extract commonly repeated test patterns into helper methods - -If you find yourself writing the same 3+ lines more than twice (e.g., listing blob names, creating a user and getting the ID, uploading test data), extract it into a descriptive helper method. This improves readability and reduces maintenance burden. - -### DO NOT leave `System.out.println` or debug logging statements in test code - -Remove all diagnostic print statements (`System.out.println`, `System.err.println`, `e.printStackTrace()`) before committing. If you need to inspect state during development, use proper assertions instead. Debug output clutters test logs, makes failures harder to diagnose, and indicates incomplete cleanup. - -### NEVER use `Thread.sleep()` to wait for containers to start - -TestContainers' `.start()` already waits for exposed ports to be ready. If you need additional readiness checks, use `.waitingFor(Wait.forListeningPort())` or `.waitingFor(Wait.forLogMessage(...))` on the container definition. `Thread.sleep()` is a flakiness time bomb — too short on slow CI, too long on fast machines. - -**Anti-pattern — DO NOT DO THIS:** -```java -// WRONG: Arbitrary sleep after container start -container.start(); -Thread.sleep(2000); // flaky — may be too short or too long -``` - -**Correct pattern:** -```java -// RIGHT: Use TestContainers built-in wait strategy -static final GenericContainer myContainer = new GenericContainer<>("image:latest") - .withExposedPorts(PORT) - .waitingFor(Wait.forListeningPort()); // Reliable readiness check -``` diff --git a/.github/skills/integration-tests/references/layer2-runner-script-templates.md b/.github/skills/integration-tests/references/layer2-runner-script-templates.md deleted file mode 100644 index 76b69ef0a..000000000 --- a/.github/skills/integration-tests/references/layer2-runner-script-templates.md +++ /dev/null @@ -1,287 +0,0 @@ -# Layer 2 Runner Script Templates - -## Script Role - -The runner script is a **test runner only**. It starts infrastructure, builds the app, runs smoke tests, and reports results. It does NOT manage git commits or handle test failures — that is the agent's responsibility. - -## When to Use - -- **Agent execution (during Layer 2 generation):** After creating the auth commit (Step 4), the agent executes this script to run smoke tests. If tests fail, the agent analyzes errors, applies fixes, and re-runs the script. -- **User execution (manual re-runs):** Users must checkout the auth commit first, then run the script to verify smoke tests still pass. - -## Placeholders to Replace - -When generating the scripts, replace these placeholders with project-specific values: - -- ``: The build command for the detected project type - - Maven: `mvn package -DskipTests` - - Gradle: `./gradlew build -x test` - - .NET: `dotnet build` - - npm: `npm run build` - -- ``: The command to start the application - - Maven (Spring Boot): `mvn spring-boot:run` or `java -jar target/*.jar` - - Gradle (Spring Boot): `./gradlew bootRun` or `java -jar build/libs/*.jar` - - .NET: `dotnet run` or `dotnet MyApp.dll` - - npm: `npm start` - -## Bash Template (run-layer2-tests.sh) - -**NOTE:** This script should be placed in `{modernization-work-folder}/integration-tests/`. The script navigates to the project root to execute commands. - -```bash -#!/bin/bash -set -e - -# --- Auto-generated Layer 2 smoke test runner --- -# Project: {ProjectName} ({Language}, {Framework}) -# Starts infrastructure, builds and runs the app, verifies it stays alive -# and responds to HTTP requests without fatal errors. -# -# Usage: -# bash {modernization-work-folder}/integration-tests/run-layer2-tests.sh -# -# Environment variable overrides (optional): -# APP_PORT - Application port (default: 8080) -# HEALTH_PATH - Health endpoint (default: /) - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" # Navigate to project root -COMPOSE_FILE="$PROJECT_ROOT/src/test/resources/docker-compose.smoke.yml" -APP_LOG="/tmp/app-smoke-$$.log" - -APP_PORT="${APP_PORT:-8080}" -APP_URL="http://localhost:$APP_PORT" -HEALTH_PATH="${HEALTH_PATH:-/health}" # Adjust to detected health endpoint -STARTUP_TIMEOUT=60 -STABILITY_WAIT=5 -CHECKS_PASSED=0 -CHECKS_FAILED=0 -APP_PID="" - -pass() { echo " ✅ $1"; CHECKS_PASSED=$((CHECKS_PASSED + 1)); } -fail() { echo " ❌ $1"; CHECKS_FAILED=$((CHECKS_FAILED + 1)); } - -cleanup() { - [ -n "$APP_PID" ] && kill $APP_PID 2>/dev/null || true - [ -n "$APP_PID" ] && wait $APP_PID 2>/dev/null || true - docker compose -f "$COMPOSE_FILE" down -v 2>/dev/null || true - rm -f "$APP_LOG" -} -trap cleanup EXIT - -cd "$PROJECT_ROOT" - -# --- Step 1: Verify Docker --- -docker info > /dev/null 2>&1 || { echo "ERROR: Docker is not running."; exit 1; } - -# --- Step 2: Start infrastructure --- -echo "Starting infrastructure..." -docker compose -f "$COMPOSE_FILE" up -d --wait - -# --- Step 3: Build --- -echo "Building application..." - - -# --- Step 4: Start application --- -echo "Starting application..." - > "$APP_LOG" 2>&1 & -APP_PID=$! - -# --- Step 5: Check — Process alive --- -sleep $STABILITY_WAIT -if kill -0 $APP_PID 2>/dev/null; then - pass "Process is alive (PID $APP_PID)" -else - fail "Process crashed on startup" - tail -20 "$APP_LOG" 2>/dev/null || true - exit 1 -fi - -# --- Step 6: Check — Health probe --- -HEALTH_STATUS="000" -for i in $(seq 1 $STARTUP_TIMEOUT); do - HEALTH_STATUS=$(curl -sf -o /dev/null -w "%{http_code}" "${APP_URL}${HEALTH_PATH}" 2>/dev/null || echo "000") - [ "$HEALTH_STATUS" != "000" ] && break - kill -0 $APP_PID 2>/dev/null || { fail "Process died during health probe"; break; } - sleep 1 -done -[[ "$HEALTH_STATUS" =~ ^[1234] ]] && pass "Health probe (HTTP $HEALTH_STATUS)" \ - || fail "Health probe failed (HTTP $HEALTH_STATUS)" - -# --- Step 7: Check — Clean startup logs --- -if grep -qiE "FATAL|panic|unhandled.exception|OutOfMemory|StackOverflow|segfault" "$APP_LOG" 2>/dev/null; then - fail "Fatal error patterns in startup logs" -else - pass "No fatal errors in startup logs" -fi - -# --- Summary --- -echo "" -echo "========================================" -echo "Results: $CHECKS_PASSED passed, $CHECKS_FAILED failed" -if [ $CHECKS_FAILED -gt 0 ]; then - echo "❌ Layer 2 smoke tests FAILED" - exit 1 -fi -echo "✅ Layer 2 smoke tests PASSED" -echo "========================================" -``` - -## PowerShell Template (run-layer2-tests.ps1) - -**NOTE:** This script should be placed in `{modernization-work-folder}/integration-tests/`. The script navigates to the project root to execute commands. - -```powershell -#Requires -Version 5.1 -$ErrorActionPreference = "Stop" - -# --- Auto-generated Layer 2 smoke test runner --- -# Project: {ProjectName} ({Language}, {Framework}) -# Starts infrastructure, builds and runs the app, verifies it stays alive -# and responds to HTTP requests without fatal errors. -# -# Usage: -# powershell {modernization-work-folder}/integration-tests/run-layer2-tests.ps1 -# -# Environment variable overrides (optional): -# APP_PORT - Application port (default: 8080) -# HEALTH_PATH - Health endpoint (default: /) - -$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path -$ProjectRoot = Resolve-Path (Join-Path $ScriptDir "..\..") # Navigate to project root -$ComposeFile = Join-Path $ProjectRoot "src\test\resources\docker-compose.smoke.yml" -$AppLog = Join-Path $env:TEMP "app-smoke-$PID.log" - -$AppPort = if ($env:APP_PORT) { $env:APP_PORT } else { 8080 } -$AppUrl = "http://localhost:$AppPort" -$HealthPath = if ($env:HEALTH_PATH) { $env:HEALTH_PATH } else { "/health" } -$StartupTimeout = 60 -$StabilityWait = 5 -$ChecksPassed = 0 -$ChecksFailed = 0 -$AppProcess = $null - -function Pass($message) { - Write-Host " ✅ $message" -ForegroundColor Green - $script:ChecksPassed++ -} - -function Fail($message) { - Write-Host " ❌ $message" -ForegroundColor Red - $script:ChecksFailed++ -} - -function Cleanup { - if ($AppProcess -and !$AppProcess.HasExited) { - Stop-Process -Id $AppProcess.Id -Force -ErrorAction SilentlyContinue - Wait-Process -Id $AppProcess.Id -ErrorAction SilentlyContinue - } - docker compose -f $ComposeFile down -v 2>$null - if (Test-Path $AppLog) { Remove-Item $AppLog -Force } -} - -# Register cleanup -try { - Set-Location $ProjectRoot - - # --- Step 1: Verify Docker --- - docker info >$null 2>&1 - if ($LASTEXITCODE -ne 0) { - Write-Host "ERROR: Docker is not running." -ForegroundColor Red - exit 1 - } - - # --- Step 2: Start infrastructure --- - Write-Host "Starting infrastructure..." - docker compose -f $ComposeFile up -d --wait - - # --- Step 3: Build --- - Write-Host "Building application..." - - - # --- Step 4: Start application --- - Write-Host "Starting application..." - $AppProcess = Start-Process -FilePath -RedirectStandardOutput $AppLog -RedirectStandardError $AppLog -PassThru -NoNewWindow - - # --- Step 5: Check — Process alive --- - Start-Sleep -Seconds $StabilityWait - if (!$AppProcess.HasExited) { - Pass "Process is alive (PID $($AppProcess.Id))" - } else { - Fail "Process crashed on startup" - if (Test-Path $AppLog) { Get-Content $AppLog -Tail 20 } - exit 1 - } - - # --- Step 6: Check — Health probe --- - $HealthStatus = "000" - for ($i = 1; $i -le $StartupTimeout; $i++) { - try { - $response = Invoke-WebRequest -Uri "$AppUrl$HealthPath" -UseBasicParsing -TimeoutSec 1 -ErrorAction SilentlyContinue - $HealthStatus = $response.StatusCode - break - } catch { - $HealthStatus = "000" - } - if ($AppProcess.HasExited) { - Fail "Process died during health probe" - break - } - Start-Sleep -Seconds 1 - } - if ($HealthStatus -match "^[1234]") { - Pass "Health probe (HTTP $HealthStatus)" - } else { - Fail "Health probe failed (HTTP $HealthStatus)" - } - - # --- Step 7: Check — Clean startup logs --- - if (Test-Path $AppLog) { - $logContent = Get-Content $AppLog -Raw - if ($logContent -match "(?i)(FATAL|panic|unhandled.?exception|OutOfMemory|StackOverflow|segfault)") { - Fail "Fatal error patterns in startup logs" - } else { - Pass "No fatal errors in startup logs" - } - } - - # --- Summary --- - Write-Host "" - Write-Host "========================================" - Write-Host "Results: $ChecksPassed passed, $ChecksFailed failed" - if ($ChecksFailed -gt 0) { - Write-Host "❌ Layer 2 smoke tests FAILED" -ForegroundColor Red - exit 1 - } - Write-Host "✅ Layer 2 smoke tests PASSED" -ForegroundColor Green - Write-Host "========================================" -} finally { - Cleanup -} -``` - -## Script Behavior - -### What the Script Does - -1. **Verify Docker** — Check if Docker is running -2. **Start infrastructure** — `docker compose up` with all dependencies -3. **Build application** — Run project-specific build command -4. **Start application** — Run in background, log to temp file -5. **Check: Process alive** — Verify process didn't crash (5s stability wait) -6. **Check: Health probe** — HTTP health check (60s timeout, any non-5xx = pass) -7. **Check: Clean logs** — Scan for fatal error patterns (FATAL, panic, OOM, etc.) -8. **Report results** — Print pass/fail summary -9. **Cleanup** — Kill app, docker down, remove temp log - -### Exit Codes - -- `0` — All checks passed -- `1` — One or more checks failed - -### Environment Variables (Optional) - -Users can override defaults: -- `APP_PORT` — Application port (default: 8080) -- `HEALTH_PATH` — Health endpoint path (default: /health) diff --git a/.github/skills/integration-tests/references/layer2-smoke-tests.md b/.github/skills/integration-tests/references/layer2-smoke-tests.md deleted file mode 100644 index f85215ee1..000000000 --- a/.github/skills/integration-tests/references/layer2-smoke-tests.md +++ /dev/null @@ -1,261 +0,0 @@ -# Layer 2: Smoke Tests - -**Goal:** "Does the whole application start and stay alive?" - -Verify the complete application boots successfully, reaches a healthy state, and produces no crash-level errors. Layer 2 does **NOT** test individual features, APIs, or business logic — that is Layer 1's responsibility. - -## What Layer 2 Is and Is Not - -| Layer 2 IS | Layer 2 IS NOT | -|------------|----------------| -| Verifying the process starts without crashing | Testing specific API endpoints or features | -| Checking a single health/readiness probe | Hitting every REST route or gRPC method | -| Scanning startup logs for fatal errors | Asserting on response payloads or data | -| Detecting missing configuration or broken DI | Exercising SDK integrations or components | -| Provisioning ALL infrastructure the app needs | Only setting up migrated-component containers | - -## No Test Classes Required - -Layer 2 does **NOT** generate test classes. The runner script (`run-layer2-tests.sh` / `run-layer2-tests.ps1`) **is** the test — it provisions infrastructure, starts the app, runs health checks via `curl` and shell built-ins, and reports pass/fail. No JUnit test code. - -**Layer 2 is independent of Layer 1.** Layer 1 tests only migrated components; Layer 2 must provision ALL application dependencies. - -## Prerequisites - -- Application builds successfully -- Docker installed and running - -## Azure Authentication for Smoke Tests - -Migrated applications typically use **Managed Identity** (`DefaultAzureCredential`) which only works on Azure infrastructure. For local smoke testing, you must modify the application to connect to local emulators instead. - -**See [azure-auth-strategies.md](./azure-auth-strategies.md) for:** -- How to classify Azure dependencies (Emulatable, Lazy, Non-emulatable) -- Emulator images and configuration -- Authentication modification strategies (config-driven vs code-driven apps) -- Emulator connection string values -- Decision flow for handling each dependency type - -**Summary:** For each Azure dependency, determine if it's emulatable (use local emulator), lazy (skip), or startup-required non-emulatable (disable via config or recommend Layer 3). - -## Workflow - -> **CRITICAL — Commit order matters.** Follow Steps 1–8 in exact order. The artifacts commit (Step 3) MUST come before the auth commit (Step 4). Do not combine them. Do not reorder them. - -The workflow produces **a multi-commit sequence** (exactly 3 commits): - -| # | Commit | Contains | -|---|--------|----------| -| 1 | `[layer-2] Add smoke test artifacts` | docker-compose.smoke.yml, runner scripts, AND any source code fixes needed for smoke tests to pass. **All fixes (infrastructure, runner scripts, source code) must be amended to this commit.** | -| 2 | `[smoke-test] Replace Managed Identity with emulator connection strings` | Config file changes (e.g., `application.properties`) with explicit emulator connection strings, and any necessary auth-related code changes. **Auth-related fixes must be amended to this commit.** | -| 3 | `[smoke-test] Restore Managed Identity auth` | Reverts **only commit 2** by SHA — restores original Managed Identity config | - -**CRITICAL — Amend strategy:** -- Fixes to docker-compose.smoke.yml → amend commit 1 -- Fixes to runner scripts → amend commit 1 -- Source code bugs discovered during testing (DI wiring, null refs, etc.) → **amend commit 1** -- Fixes to auth config files → amend commit 2 -- Fixes to auth-related code → amend commit 2 - -This ensures when users checkout commit 2 (auth) to run smoke tests locally, they have ALL fixes: commit 1 contains working artifacts AND all source code fixes, commit 2 contains working auth config. - -The agent executes the smoke test runner script (Step 7) after creating commit 2, and may need to amend commits 1 or 2 during retries. - -**Retry on failure:** If the runner script fails, the agent analyzes the output and attempts a fix: -- **Smoke-artifact failure** (wrong port in compose, bad docker-compose config, bad runner script) → **amend commit 1** with fixes and re-run Step 7 -- **Source-code failure** (broken DI wiring, null reference in initializer, missing config guard, startup crash from migrated code) → **amend commit 1** with fixes and re-run Step 7. Apply the **Handling Test Failures** guidance from the main skill file. -- **Auth-config failure** (wrong connection string in config file, missing config property, auth-related code issue) → **amend commit 2** with fixes and re-run Step 7 - -Max **3 retries** total (across all failure types). If the agent cannot diagnose the root cause or the fix would require architectural changes, stop and report. - -### Step 1: Analyze ALL Application Dependencies - -Scan the **entire** application to identify every external dependency required for startup: - -- Build files: `pom.xml`, `build.gradle`, `*.csproj`, `package.json` -- Config: `application.yml`, `application.properties`, `appsettings.json`, `.env` -- Source code: client initializations, `@Autowired`/`@Inject` services -- Existing Docker/compose files - -**For each Azure dependency, classify it** using the decision flow from [azure-auth-strategies.md](./azure-auth-strategies.md): -- **Emulatable** → Add emulator to docker-compose, will modify auth in Step 4 -- **Lazy** → Skip (doesn't block startup) -- **Non-emulatable** → Disable via config in Step 4, or recommend Layer 3 - -**For non-Azure dependencies:** - -| Category | Example | Action | -|----------|---------|--------| -| Standard databases | PostgreSQL, MySQL, MongoDB, Redis | Add to `docker-compose.smoke.yml` with standard image | -| Azure wire-compatible | Azure DB for PostgreSQL/MySQL, Azure Cache for Redis, Azure SQL | Use open-source image (`postgres:16`, `redis:7`, `mcr.microsoft.com/mssql/server`) | - -**Rules:** Include anything that crashes the app on startup. Skip lazy/on-demand dependencies. Reuse Docker images and ports from Layer 1 where applicable. If an existing `docker-compose.yml` already defines all needed services, prefer reusing it. - -### Step 2: Generate Docker Compose - -Create `docker-compose.smoke.yml` in the test resources directory. The file provisions ALL required dependencies. Every service must have a healthcheck. Include proper ports and volume mounts. - -**File location:** `src/test/resources/docker-compose.smoke.yml` - -### Step 3: Generate Runner Scripts and Commit Artifacts - -Generate `run-layer2-tests.sh` and `run-layer2-tests.ps1` (see Runner Script Template below). - -**File locations:** -- `{modernization-work-folder}/integration-tests/run-layer2-tests.sh` -- `{modernization-work-folder}/integration-tests/run-layer2-tests.ps1` - -Then commit all generated artifacts: - -``` -git add -A -git commit -m "[layer-2] Add smoke test artifacts" -``` - -This commit initially contains only generated files (docker-compose.smoke.yml, runner scripts). No source code changes yet. Files in `.github/` may be excluded if the project's `.gitignore` blocks them — this is acceptable. - -**CRITICAL:** If smoke tests fail during Step 7, amend this commit with ALL fixes: -``` -# After fixing docker-compose.smoke.yml, runner scripts, OR source code -git add -A -git commit --amend --no-edit -``` - -This includes source code bugs discovered during smoke testing. The commit message says "Add smoke test artifacts" but the commit will contain test infrastructure (docker-compose, runner scripts) AND source code fixes needed for tests to pass. This ensures users who checkout the auth commit (Step 4) will have all fixes via this commit in git history. - -### Step 4: Commit Auth Changes - -**CRITICAL:** This step is MANDATORY. You must ALWAYS create an auth commit that modifies config files. - -Modify the application configuration files (NOT environment variables) to use explicit emulator connection strings. See [azure-auth-strategies.md](./azure-auth-strategies.md) for: -- Which config files to modify (`application.properties`, `appsettings.json`, etc.) -- Emulator connection string formats -- Code modification patterns if needed - -Create the auth commit: - -```bash -git add -A -git commit -m "[smoke-test] Replace Managed Identity with emulator connection strings" -``` - -Record the commit SHA for Step 8. - -### Step 5: Verify Config File Changes - -**Do NOT modify runner scripts with environment variables.** The config files from Step 4 contain all necessary connection strings. - -Verify that the auth commit includes: -- All modified config files with explicit connection string values -- Any code changes if the app was hardcoded/plain application -- No environment variable exports or overrides - -The runner scripts will start the application as-is, and the app will read connection strings from the config files you just modified. - -### Step 6: Auto-Detect Application Type - -Analyze the project to determine how it starts and how to verify liveness: - -| Indicator | App Type | Start Command | Liveness Check | -|-----------|----------|---------------|----------------| -| Spring Boot, `@SpringBootApplication` | Java Web/API | `./mvnw spring-boot:run` or `java -jar` | HTTP health probe | -| `@Scheduled`, background jobs | Worker/Background | `./mvnw spring-boot:run` | Process stays alive for N seconds | -| `main()` with no server | CLI/Batch | `java -jar` | Process exits with code 0 | - -### Step 7: Execute Smoke Tests via Runner Script - -After creating the auth commit (Step 4) and detecting the application type (Step 6), execute the smoke test runner script: - -```bash -bash {modernization-work-folder}/integration-tests/run-layer2-tests.sh -# OR on Windows: -powershell {modernization-work-folder}/integration-tests/run-layer2-tests.ps1 -``` - -**The script will:** -1. Verify Docker is running -2. Start infrastructure (`docker compose up`) -3. Build the application -4. Start the application in background -5. Run three health checks: - - **Check 1:** Process is alive after stability wait (5s) - - **Check 2:** Health probe returns non-5xx HTTP response (web/API apps only) - - **Check 3:** No fatal error patterns in startup logs (FATAL, panic, OutOfMemory, etc.) -6. Report pass/fail and exit with appropriate code -7. Clean up (kill app, docker down) - -**If the script exits with code 0:** -- ✅ Tests passed -- Proceed to Step 8 (create restore commit) - -**If the script exits with non-zero:** -- ❌ Tests failed (likely auth-config issues) -- Analyze the script's output to determine root cause -- **Amend the auth commit (Step 5)** with fixes -- Re-run the script (max 3 retries total) - -**Important:** At this point, all source code fixes should already be done (Step 4). Failures here are typically auth configuration issues (wrong connection strings, missing config properties). - -**CRITICAL — Do NOT Create Test Classes:** -- Layer 2 does NOT use JUnit test classes -- The runner script IS the test - it uses shell commands (curl, grep, etc.) -- Do NOT create any `.java` test files for Layer 2 -- If you need to improve smoke testing, modify the runner scripts, not create test classes - -### Step 8: Create Restore Commit - -After smoke tests pass, create the restore commit to restore Managed Identity auth: - -```bash -git revert --no-edit -git commit --amend -m "[smoke-test] Restore Managed Identity auth" -``` - -This keeps the auth commit in history (visible in `git log`) while restoring the codebase to use Managed Identity. - -**CRITICAL:** All smoke test artifacts (docker-compose.smoke.yml, runner scripts) and source code fixes are in commit 1. Users can checkout the auth commit (commit 2) to run smoke tests locally and will have all necessary fixes via commit 1 in git history. - -## Handling Failures - -**CRITICAL:** All fixes must be amended to either commit 1 (smoke artifacts + source code) or commit 2 (auth config). Never create separate fix commits. - -Testing happens in Step 7 (after both commits 1 and 2 are created). When failures occur: - -| Failure | Likely Cause | Action | -|---------|-------------|--------| -| Infrastructure won't start | Port conflict, Docker issue, bad docker-compose config | **Amend commit 1** (fix docker-compose.smoke.yml) and re-run Step 7 | -| Build fails | Compile error in source code, missing dependencies | **Amend commit 1** (fix source code) and re-run Step 7. Apply Handling Test Failures guidance. | -| Process crashes immediately | Broken DI wiring, null ref, missing config guard, startup crash, OR wrong auth config | **Analyze stack trace** → amend commit 1 (source code bugs) or commit 2 (auth issues) and re-run Step 7 | -| Runner script fails | Wrong app start command, incorrect paths, bad classpath | **Amend commit 1** (fix runner scripts) and re-run Step 7 | -| Health probe times out | Wrong port in config, slow startup | **Amend commit 1** (fix runner script health check) and re-run Step 7 | -| Health probe returns 5xx | Internal error from missing config or code bug | **Analyze** → amend commit 1 (code) or commit 2 (auth) and re-run Step 7 | -| Fatal log patterns | Unhandled exceptions at startup | **Analyze stack trace** → amend commit 1 (source code) or commit 2 (auth config) if root cause is identifiable; otherwise report | -| Fatal log patterns | Unhandled exceptions at startup | **Analyze stack trace → fix** if root cause is identifiable; otherwise report | - -**Key principles:** -- Layer 2 fixes **both** its own generated artifacts **and** source-code issues it discovers. The goal is a passing smoke test, not just a report. -- Use the **Handling Test Failures** guidance from the main skill file to decide what to fix and how. -- Keep source-code fixes **minimal and targeted** — fix the specific startup blocker (e.g., null guard, missing config binding, broken DI registration). Do not refactor or change architecture. -- Max **3 retries** total. If the root cause is unclear, the fix would require architectural changes, or the app genuinely needs a real Azure service with no workaround, stop and report. - -## Standardized Runner Scripts - -Generate `run-layer2-tests.sh` and `run-layer2-tests.ps1` in `{modernization-work-folder}/integration-tests/`. These are test runners that start infrastructure, build the app, run smoke tests, and report results. - -**See [layer2-runner-script-templates.md](./layer2-runner-script-templates.md) for:** -- Complete bash and PowerShell script templates -- Placeholder replacement guide -- Script behavior and exit codes -- Environment variable overrides - -Users run: -- Unix: `bash {modernization-work-folder}/integration-tests/run-layer2-tests.sh` -- Windows: `powershell {modernization-work-folder}/integration-tests/run-layer2-tests.ps1` - -## Pass Criteria - -- All infrastructure containers reach healthy state -- Application builds and starts without crashing -- Health probe returns non-5xx (web/API apps) -- No fatal-level errors in startup logs -- Clean shutdown of app and infrastructure diff --git a/.github/skills/integration-tests/references/layer3-azure-integration.md b/.github/skills/integration-tests/references/layer3-azure-integration.md deleted file mode 100644 index df30130ef..000000000 --- a/.github/skills/integration-tests/references/layer3-azure-integration.md +++ /dev/null @@ -1,194 +0,0 @@ -# Layer 3: Azure Integration Tests - -**Goal:** "Does it work in real cloud environment?" - -Deploy to Azure staging environment and test against real Azure services. - -## Test Isolation - -- **DO use `L3Test` as the class name suffix** for all Layer 3 test classes (e.g., `AzureSqlL3Test`, `BlobStorageL3Test`). The `*Test` suffix matches Maven Surefire's default discovery pattern — no build plugin changes needed. -- **DO annotate every test class with a `Layer3` tag/category** so runner scripts filter precisely and never trigger Layer 1, 2, or 4 tests. See the Test Isolation Convention in the main skill file for the exact annotation per language/framework. -- The runner script **MUST filter by the `Layer3` tag** (e.g., `mvn verify -Dgroups=Layer3`, `dotnet test --filter Category=Layer3`, `pytest -m layer3`) - -## Prerequisites - -- Azure subscription with staging environment -- Azure CLI installed and authenticated -- Infrastructure-as-Code templates (Bicep/ARM/Terraform) - -## Workflow - -### 1. Identify Azure Services - -Map application dependencies to Azure services: - -| Local Dependency | Azure Service | Configuration Needed | -|-----------------|---------------|---------------------| -| SQL Server | Azure SQL Database | Connection string, firewall | -| PostgreSQL | Azure Database for PostgreSQL | Connection string, SSL | -| Redis | Azure Cache for Redis | Connection string, access key | -| File storage | Azure Blob Storage | Connection string, container | -| Message queue | Azure Service Bus | Connection string, queue | -| Key vault | Azure Key Vault | Managed identity, access policy | -| Logging | Application Insights | Instrumentation key | - -### 2. Deploy to Staging - -#### Using Azure CLI - -```bash -# Set variables -RESOURCE_GROUP="rg-app-staging" -LOCATION="eastus" -APP_NAME="app-integration-test-$(date +%s)" - -# Create resource group -az group create --name $RESOURCE_GROUP --location $LOCATION - -# Deploy infrastructure -az deployment group create \ - --resource-group $RESOURCE_GROUP \ - --template-file infra/main.bicep \ - --parameters environment=staging - -# Deploy application -az webapp deploy \ - --resource-group $RESOURCE_GROUP \ - --name $APP_NAME \ - --src-path ./publish.zip -``` - -#### Using GitHub Actions - -```yaml -- name: Deploy to Staging - uses: azure/webapps-deploy@v2 - with: - app-name: ${{ env.APP_NAME }} - slot-name: staging - package: ./publish.zip -``` - -### 3. Configure Azure Services - -```bash -# Get connection strings from deployed resources -SQL_CONN=$(az sql db show-connection-string \ - --server $SQL_SERVER \ - --name $DB_NAME \ - --client ado.net) - -REDIS_CONN=$(az redis list-keys \ - --resource-group $RESOURCE_GROUP \ - --name $REDIS_NAME \ - --query primaryKey -o tsv) - -STORAGE_CONN=$(az storage account show-connection-string \ - --resource-group $RESOURCE_GROUP \ - --name $STORAGE_NAME \ - --query connectionString -o tsv) - -# Update app settings -az webapp config appsettings set \ - --resource-group $RESOURCE_GROUP \ - --name $APP_NAME \ - --settings \ - "ConnectionStrings__Default=$SQL_CONN" \ - "Redis__ConnectionString=$REDIS_CONN" \ - "Storage__ConnectionString=$STORAGE_CONN" -``` - -### 4. Run Integration Tests Against Staging - -```bash -# Set test target to staging URL -export TEST_BASE_URL="https://$APP_NAME.azurewebsites.net" - -# Run Layer 3 integration tests only (filter by Layer3 tag) -dotnet test --filter Category=Layer3 -``` - -Test scenario areas for Azure (all tagged with `Layer3`): - -| Scenario Area | Tests | -|----------|-------| -| Database | CRUD operations, transactions, stored procedures | -| Storage | Blob upload/download, container operations | -| Cache | Get/set, expiration, distributed locks | -| Messaging | Send/receive, dead letter, retry | -| Identity | Authentication, authorization, managed identity | - -### 5. Validate Azure-Specific Features - -```csharp -[Trait("Category", "Layer3")] -public class AzureSqlL3Test -{ - [Fact] - public async Task CanConnectToAzureSql() - { - await using var conn = new SqlConnection(_connectionString); - await conn.OpenAsync(); - conn.State.Should().Be(ConnectionState.Open); - } - - [Fact] - public async Task TransactionWorksAcrossMultipleTables() - { - // Test Azure SQL transaction behavior - } -} - -[Trait("Category", "Layer3")] -public class AzureBlobL3Test -{ - [Fact] - public async Task CanUploadAndDownloadBlob() - { - var container = _blobClient.GetBlobContainerClient("test"); - await container.CreateIfNotExistsAsync(); - - var blob = container.GetBlobClient("test.txt"); - await blob.UploadAsync(new BinaryData("test content")); - - var downloaded = await blob.DownloadContentAsync(); - downloaded.Value.Content.ToString().Should().Be("test content"); - } -} -``` - -### 6. Collect Metrics and Logs - -```bash -# Get Application Insights logs -az monitor app-insights query \ - --app $APP_INSIGHTS_NAME \ - --analytics-query "traces | where timestamp > ago(1h) | order by timestamp desc | take 100" - -# Check for errors -az monitor app-insights query \ - --app $APP_INSIGHTS_NAME \ - --analytics-query "exceptions | where timestamp > ago(1h)" - -# Get performance metrics -az monitor metrics list \ - --resource $APP_RESOURCE_ID \ - --metric "Requests,AverageResponseTime,Http5xx" -``` - -### 7. Cleanup - -```bash -# Delete staging resources -az group delete --name $RESOURCE_GROUP --yes --no-wait -``` - -## Pass Criteria - -- Application deploys successfully to Azure -- All Azure service connections work -- Integration tests pass against real services -- No 5xx errors in Application Insights -- Performance is within acceptable thresholds -- Logs show no critical errors -- Runner scripts generated per the Standardized Runner Scripts convention in the main skill file diff --git a/.github/skills/integration-tests/references/layer4-behavioral-comparison.md b/.github/skills/integration-tests/references/layer4-behavioral-comparison.md deleted file mode 100644 index 0eceb1c71..000000000 --- a/.github/skills/integration-tests/references/layer4-behavioral-comparison.md +++ /dev/null @@ -1,242 +0,0 @@ -# Layer 4: Behavioral Comparison - -**Goal:** "Does it match the original?" - -Run old and new versions side-by-side and compare outputs for identical inputs. - -## Test Isolation - -- **DO use `L4Test` as the class name suffix** for all Layer 4 behavioral comparison test classes (e.g., `OrderApiL4Test`, `UserServiceL4Test`). The `*Test` suffix matches Maven Surefire's default discovery pattern — no build plugin changes needed. -- **DO annotate every test class with a `Layer4` tag/category** so runner scripts filter precisely and never trigger Layer 1, 2, or 3 tests. See the Test Isolation Convention in the main skill file for the exact annotation per language/framework. -- The runner script **MUST filter by the `Layer4` tag** (e.g., `mvn verify -Dgroups=Layer4`, `dotnet test --filter Category=Layer4`, `pytest -m layer4`) - -## Prerequisites - -- Access to both original and migrated application code -- Ability to run both versions simultaneously -- Test data/scenarios from production or comprehensive test suite - -## Workflow - -### 1. Set Up Side-by-Side Environment - -``` -┌─────────────────┐ ┌─────────────────┐ -│ Original App │ │ Migrated App │ -│ (Port 8080) │ │ (Port 8081) │ -└────────┬────────┘ └────────┬────────┘ - │ │ - └───────────┬───────────┘ - │ - ┌──────┴──────┐ - │ Comparator │ - │ Tests │ - └─────────────┘ -``` - -#### Docker Compose Setup - -```yaml -version: '3.8' -services: - original-app: - build: - context: ./original - ports: - - "8080:80" - environment: - - ConnectionStrings__Default=${DB_CONNECTION} - - migrated-app: - build: - context: ./migrated - ports: - - "8081:80" - environment: - - ConnectionStrings__Default=${DB_CONNECTION} - - # Shared database for consistent state - database: - image: mcr.microsoft.com/mssql/server:2022-latest - environment: - - ACCEPT_EULA=Y - - SA_PASSWORD=YourStrong!Passw0rd -``` - -### 2. Define Comparison Test Cases - -Identify all testable behaviors: - -| Category | Test Inputs | Comparison Points | -|----------|-------------|-------------------| -| API responses | Same HTTP requests | Status code, response body, headers | -| Data operations | Same CRUD operations | Database state, return values | -| Business logic | Same input parameters | Calculation results, decisions | -| Error handling | Invalid inputs | Error codes, messages | -| Edge cases | Boundary values | Behavior consistency | - -### 3. Create Comparison Test Framework - -```csharp -public class BehaviorComparisonTests -{ - private readonly HttpClient _originalClient; - private readonly HttpClient _migratedClient; - - public BehaviorComparisonTests() - { - _originalClient = new HttpClient { BaseAddress = new Uri("http://localhost:8080") }; - _migratedClient = new HttpClient { BaseAddress = new Uri("http://localhost:8081") }; - } - - [Theory] - [MemberData(nameof(GetTestCases))] - public async Task ResponsesMatch(TestCase testCase) - { - // Execute same request against both - var originalResponse = await ExecuteRequest(_originalClient, testCase); - var migratedResponse = await ExecuteRequest(_migratedClient, testCase); - - // Compare responses - var comparison = CompareResponses(originalResponse, migratedResponse); - - comparison.IsMatch.Should().BeTrue( - $"Mismatch in {testCase.Name}:\n{comparison.Differences}"); - } - - private ComparisonResult CompareResponses(Response original, Response migrated) - { - var differences = new List(); - - // Compare status codes - if (original.StatusCode != migrated.StatusCode) - differences.Add($"Status: {original.StatusCode} vs {migrated.StatusCode}"); - - // Compare response bodies (with normalization) - var normalizedOriginal = NormalizeResponse(original.Body); - var normalizedMigrated = NormalizeResponse(migrated.Body); - - if (!JsonEquals(normalizedOriginal, normalizedMigrated)) - differences.Add($"Body differs:\nOriginal: {normalizedOriginal}\nMigrated: {normalizedMigrated}"); - - return new ComparisonResult - { - IsMatch = differences.Count == 0, - Differences = string.Join("\n", differences) - }; - } - - private string NormalizeResponse(string json) - { - // Remove fields that are expected to differ - var obj = JsonSerializer.Deserialize(json); - return RemoveIgnoredFields(obj, new[] { "timestamp", "requestId", "version" }); - } -} -``` - -### 4. Handle Expected Differences - -Some differences are expected and should be ignored: - -| Field Type | Example | Handling | -|-----------|---------|----------| -| Timestamps | `createdAt`, `updatedAt` | Ignore or compare format only | -| IDs | `requestId`, `correlationId` | Ignore | -| Version info | `apiVersion`, `buildNumber` | Ignore | -| Order | Array element order | Sort before compare | -| Precision | Float/decimal precision | Round to acceptable precision | - -```csharp -public class ResponseNormalizer -{ - private readonly HashSet _ignoredFields = new() - { - "timestamp", "createdAt", "updatedAt", - "requestId", "correlationId", "traceId", - "version", "buildNumber" - }; - - public JsonElement Normalize(JsonElement element) - { - // Remove ignored fields - // Sort arrays - // Normalize number precision - // etc. - } -} -``` - -### 5. Generate Comparison Report - -```markdown -# Behavioral Comparison Report - -## Summary -- Total test cases: 150 -- Matching: 147 (98%) -- Differences: 3 (2%) - -## Differences Found - -### Case: GET /api/orders/123 -| Field | Original | Migrated | -|-------|----------|----------| -| `items[0].price` | 19.99 | 19.990000 | - -**Analysis:** Floating point precision difference, acceptable. - -### Case: POST /api/users (duplicate email) -| Aspect | Original | Migrated | -|--------|----------|----------| -| Status | 400 | 409 | -| Message | "Email exists" | "Duplicate email" | - -**Analysis:** Different but semantically equivalent error handling. - -## Recommendations -1. Accept precision differences as expected -2. Review error message changes with stakeholders -3. All critical business logic matches ✓ -``` - -### 6. Run Comparison Suite - -```bash -# Start both applications -docker-compose up -d - -# Wait for both to be healthy -./wait-for-healthy.sh http://localhost:8080/health -./wait-for-healthy.sh http://localhost:8081/health - -# Run Layer 4 comparison tests only (filter by Layer4 tag) -dotnet test --filter Category=Layer4 - -# Generate report -dotnet test --logger "html;LogFileName=comparison-report.html" - -# Cleanup -docker-compose down -``` - -## Handling Database State - -For stateful comparisons, ensure both applications start with identical data: - -```bash -# Reset database before each test -docker-compose exec database /opt/mssql-tools/bin/sqlcmd \ - -S localhost -U sa -P 'YourStrong!Passw0rd' \ - -i /scripts/reset-test-data.sql -``` - -## Pass Criteria - -- All API responses match (after normalization) -- Database state changes are identical -- Business logic produces same results -- Error handling is semantically equivalent -- Performance is comparable (within 20% variance) -- No regressions in functionality -- Runner scripts generated per the Standardized Runner Scripts convention in the main skill file diff --git a/.github/skills/rearchitect/SKILL.md b/.github/skills/rearchitect/SKILL.md deleted file mode 100644 index 7a94508ec..000000000 --- a/.github/skills/rearchitect/SKILL.md +++ /dev/null @@ -1,126 +0,0 @@ ---- -name: rearchitect -description: Scan project for Apache Struts and WinForms usage, report findings with modern alternatives ---- - -# Rearchitect — Legacy Framework Detection - -Scan the current project to detect outdated or unmaintained frameworks/technology stacks, report findings, and suggest modern alternatives. Save results via the `write_assessment_result` tool. - -## Input Parameters - -- `workspace-path` (optional): Path to the project to analyze (defaults to current directory) - -## Execution Steps - -### Step 1: Determine Project Type - -Identify the project's technology stack by looking for marker files: - -| Marker File | Project Type | -|-------------|-------------| -| `pom.xml`, `build.gradle`, `build.gradle.kts` | Java/JVM | -| `*.csproj`, `*.sln`, `*.slnx` | .NET | -| `web.xml` | Java Web (Servlet) | - -### Step 2: Run Detection - -**Only** check the two targets listed below. Do **not** scan for or report any other frameworks, libraries, or dependencies beyond these two targets. - ---- - -#### Required Targets - -##### 1. Apache Struts - -**Configuration file detection:** -- `pom.xml` / `build.gradle` / `build.gradle.kts`: search for `org.apache.struts`, `struts2-core`, `struts-core`, `struts-taglib` -- `web.xml`: search for `org.apache.struts`, `StrutsPrepareAndExecuteFilter`, `ActionServlet` - -**Source code detection:** -- Java files: search for `import org.apache.struts`, `import com.opensymphony.xwork2` -- JSP files: search for `<%@ taglib.*struts`, ` Apache Struts has reached end-of-life and no longer receives security patches or bug fixes. It has a history of critical remote code execution vulnerabilities (e.g., CVE-2017-5638, CVE-2023-50164) that made it one of the most exploited frameworks in the Java ecosystem. Continued use exposes the application to known, unpatched attack vectors. Migration to a modern, actively maintained framework such as Spring Boot is strongly recommended to ensure ongoing security support and access to current Java platform features. - -##### 2. WinForms (Windows Forms) - -**Configuration file detection:** -- `.csproj`: search for `true`, `System.Windows.Forms`, `WinExe` (combined with WindowsForms references) - -**Source code detection:** -- C# files: search for `using System.Windows.Forms`, `: Form`, `: UserControl` (in System.Windows.Forms context) -- Presence of `.Designer.cs` files - -**Alternatives:** WPF, MAUI, Avalonia UI, or Blazor Desktop - -**Fixed explanation (use verbatim when detected):** -> Windows Forms (WinForms) is a legacy UI framework tied exclusively to Windows and .NET Framework. While it still receives minimal maintenance in .NET 8+, it lacks modern UI capabilities such as responsive layouts, high-DPI scaling, hardware-accelerated rendering, and cross-platform support. Its designer-centric, event-driven programming model makes it difficult to adopt modern patterns like MVVM or data binding. Migrating to WPF is recommended for richer user experiences, better maintainability, and modern UI patterns. - ---- - -### Step 3: Save Output - -Call the `write_assessment_result` tool with the following parameters: -- `resultJson`: the detection results as a JSON string -- `assessmentDir`: the value of the `assessment_dir` variable - -The JSON must strictly follow this format: - -```json -{ - "outdatedFrameworks": [ - { - "name": "Apache Struts 2", - "old": "struts2", - "new": "spring-boot", - "status": "end-of-life", - "detectedVersion": "2.5.30", - "migrationComplexity": "high", - "detectedIn": { - "configFiles": ["path/to/pom.xml"], - "sourceFiles": ["path/to/Action.java", "path/to/Login.jsp"] - }, - "alternatives": [ - "Spring Boot + Spring MVC", - "Quarkus", - "Micronaut" - ], - "explanation": "Apache Struts has reached end-of-life and no longer receives security patches or bug fixes. It has a history of critical remote code execution vulnerabilities (e.g., CVE-2017-5638, CVE-2023-50164) that made it one of the most exploited frameworks in the Java ecosystem. Continued use exposes the application to known, unpatched attack vectors. Migration to a modern, actively maintained framework such as Spring Boot is strongly recommended to ensure ongoing security support and access to current Java platform features." - } - ] -} -``` - -**JSON field descriptions:** -- `name`: Full framework name (with version distinction, e.g. "Apache Struts 1" vs "Apache Struts 2") -- `old`: Short identifier of the legacy framework (e.g. `struts2`, `winforms`) -- `new`: Short identifier of the recommended primary alternative (e.g. `spring-boot`, `maui`, `blazor`) -- `status`: `"end-of-life"` | `"deprecated"` | `"unmaintained"` | `"security-vulnerability"` | `"critical-bug"` | `"runtime-incompatible"` -- `detectedVersion`: Version string detected from config files, or `null` if not determinable -- `migrationComplexity`: `"high"` | `"medium"` | `"low"` -- `detectedIn.configFiles`: List of config file paths where references were detected -- `detectedIn.sourceFiles`: List of source file paths where references were detected -- `alternatives`: List of all recommended alternative frameworks -- `explanation`: Fixed explanation text for why this framework needs to be upgraded — use the exact text specified in each target's "Fixed explanation" section above - -If no outdated frameworks are detected, pass `{"outdatedFrameworks": []}` to the tool. - -**The JSON passed to the tool must contain ONLY the JSON object described above. No other text or formatting.** - -## Error Handling - -- **Unsupported project type**: Output a single line: `> ERROR: Unsupported project type. This skill supports Java, .NET, JavaScript, and TypeScript projects only.` -- **No build files found**: Output: `> ERROR: No recognized build files found at {workspace-path}. Verify the path is correct.` -- **Insufficient info**: Generate a best-effort report from available data. Set `detectedVersion` to `null` for dependencies where the version cannot be determined. - -## Success Criteria - -- All detection targets are checked against both configuration files and source code -- No frameworks or dependencies beyond Apache Struts and WinForms are reported -- JSON is valid and follows the specified schema exactly -- Only high-confidence findings are reported — no guesses or uncertain cases -- Result saved via `write_assessment_result` tool diff --git a/.github/skills/reconcile-tasks-with-plan/SKILL.md b/.github/skills/reconcile-tasks-with-plan/SKILL.md deleted file mode 100644 index c63c67210..000000000 --- a/.github/skills/reconcile-tasks-with-plan/SKILL.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -name: reconcile-tasks-with-plan -description: Update tasks.json to match the current plan.md content without modifying plan.md ---- - -# Reconcile tasks.json with plan.md - -This skill synchronizes tasks.json with plan.md when the plan has been manually updated. -It reads the current plan.md and tasks.json, detects differences, and rewrites tasks.json -to reflect the plan. **Do NOT modify plan.md** — it is the source of truth for task intent. - -## User Input - -- modernization-work-folder (Mandatory): The folder containing plan.md and .metadata/tasks.json -- language (Mandatory): The programming language of the project (java or dotnet) - -## Workflow - -1. **Read current state**: Read the following files from `${modernization-work-folder}`: - - `plan.md` — the authoritative modernization plan (source of truth for task intent) - - `.metadata/tasks.json` — the current structured task list (source of truth for execution state) - -2. **Detect inconsistencies**: Compare plan.md against the tasks defined in .metadata/tasks.json. Look for: - - Tasks described in plan.md that are missing from tasks.json - - Tasks in tasks.json that are no longer mentioned in plan.md - - Changes in task descriptions, requirements, scope, or ordering between the two files - -3. **Update tasks.json**: If inconsistencies are found, rewrite .metadata/tasks.json to match plan.md: - - Refer to the json schema tasks-schema.json for the correct task structure - - Add new tasks that appear in plan.md but are missing from tasks.json - - Remove tasks that are no longer described in plan.md - - Update descriptions, requirements, or ordering to match plan.md - - **Preserve task IDs**: For tasks that clearly map to existing entries, keep the same task ID - - **Preserve execution state**: Do NOT reset tasks that already have a `status` of "success", "failed", or "skipped". Keep their `status`, `taskSummary`, and `successCriteriaStatus` unchanged - - **Preserve metadata**: Keep the `metadata` block unchanged (same `planName`, `createdAt`, etc.) - - Follow the same task breakdown rules and schema conventions as `create-modernization-plan` - - Consult `java-upgrade-guideline.md` or `dotnet-upgrade-guideline.md` as applicable for upgrade tasks - -4. **Skip if consistent**: If no meaningful inconsistencies are found between plan.md and tasks.json, do not write any files. Simply respond that no changes were needed. - -## Important Notes - -- **Never modify plan.md** — it is the user's manually edited source of truth -- Only modify tasks that are genuinely affected by changes in plan.md -- Each task must remain independently testable -- Do not change task types unless the plan.md clearly indicates a different type diff --git a/.github/skills/repository-dependency-graph/SKILL.md b/.github/skills/repository-dependency-graph/SKILL.md deleted file mode 100644 index 76b1e1dd4..000000000 --- a/.github/skills/repository-dependency-graph/SKILL.md +++ /dev/null @@ -1,250 +0,0 @@ ---- -name: repository-dependency-graph -description: Analyze the service topology of a multi-repository application. Given an application name and repository paths, identifies each independently deployable service, infers each service's role and language, detects inter-service dependencies, and produces a topology graph (Markdown). ---- - -# Repository Dependency Graph - - - -Analyze the service topology of the application identified by the `name` parameter. For each -repository in `entries`, identify the independently deployable services it contains, infer their -roles and languages, and detect inter-service dependencies. Produce a topology graph in Markdown -format and save it to `{report-dir}/application-topology-graph.md`. - -## Input Parameters - -- `name` (required): The short, human-readable display name for the application. Used as the - report heading and in the service table. -- `entries` (required, list): One or more repository entries belonging to this application. Each - entry is an object with: - - `repoName` (required): The canonical display name for the repository (e.g. `order-service`). - Used in the topology graph, service table, and as the basis for slug generation. Takes - precedence over the directory name for all naming and dependency-matching purposes. - - `path` (required): The local filesystem path to the repository root. - - At least one entry must have an accessible `path`. -- `report-dir` (required): The directory where the output file is written. The directory is - created and managed by the CLI runner; the skill must not modify its path or structure. - -**Output filename**: `{report-dir}/application-topology-graph.md` - -## Execution Steps - -### Step 1: Validate Inputs - -1. Verify `name` is non-empty. If empty, abort with: - `> ERROR: The 'name' parameter is required and must not be empty.` -2. For each entry in `entries`, verify both `repoName` and `path` are present and non-empty. If - either is missing, abort with: - `> ERROR: Each entry in 'entries' must have both 'repoName' and 'path' fields.` -3. For each entry in `entries`, check whether `path` is accessible (exists and is readable). - - Collect accessible entries into a working set. - - Collect inaccessible entries into an `inaccessible` list for reporting. -4. If the working set is empty (all paths are inaccessible), abort with: - `> ERROR: Topology analysis skipped for '{name}': none of the provided repository paths could be accessed.` -5. If `report-dir` does not exist or is not writable, abort with: - `> ERROR: Output directory '{report-dir}' does not exist or is not writable.` - -### Step 2: Per-Repository Service Identification - -For each accessible entry in `entries`, identify independently deployable services using `entry.path` -as the filesystem root. Use `entry.repoName` as the canonical repository name — it replaces -directory-name inference for display labels, slug generation, and cross-repository dependency -matching. - -**Build file detection** (determines language, framework, and service candidates): - -| Build file | Language | Framework hints | -|------------|----------|-----------------| -| `pom.xml` | java | Check `` for `spring-boot-starter-parent` → Spring Boot; `quarkus-bom` → Quarkus; `micronaut-parent` → Micronaut | -| `build.gradle` / `build.gradle.kts` | java | Check `plugins {}` for `org.springframework.boot`, `io.quarkus`, `io.micronaut.application` | -| `*.csproj` | dotnet | Check `` and packages for `Microsoft.AspNetCore.*` → ASP.NET Core; `Microsoft.Azure.Functions.Worker` → Azure Functions | -| `*.sln` / `*.slnx` | dotnet | Multi-project solution; recurse into sub-projects | -| `package.json` (with `"start"` script or `"main"` field) | javascript/typescript | Check `"dependencies"` for `express`, `fastify`, `koa`, `@nestjs/core`, `next`, `react`, `vue`, `@angular/core` | -| `tsconfig.json` (without `package.json`) | typescript | Infer TypeScript library | - -**Service identity** (one candidate per independently deployable unit): - -- For single-service repos: use `entry.repoName` as the service name. -- For monorepos: use `entry.repoName` as the repository label; detect multiple deployable units by looking for: - - Multiple `Dockerfile` files at different subdirectory levels (each names a service) - - Multiple `*.csproj` files with `Exe` or `exe` - - Multiple `pom.xml` files in subdirectories (each with its own ``) - - Multiple `package.json` files with a `"start"` or `"main"` script at subdirectory level - - Each detected unit becomes a separate Service entry; `subPath` is set to its relative path within the repo. - -**ServiceRole inference rules** (apply in order; first match wins): - -| Role | Evidence | -|------|----------| -| `ApiGateway` | Name or directory contains `gateway`, `proxy`, `edge`, `ingress`; OR Spring Cloud Gateway / Netflix Zuul dependency detected | -| `Frontend` | Framework is React, Angular, Vue, Blazor, Next.js, Nuxt; OR name contains `ui`, `web`, `frontend`, `portal`, `dashboard` | -| `Worker` | No inbound HTTP port exposed in Dockerfile; OR name contains `worker`, `consumer`, `processor`, `job`, `scheduler`, `daemon`; OR Spring Batch / Azure WebJobs / Hangfire dependency detected | -| `DataService` | Name contains `db`, `data`, `repository`, `store`, `cache`; OR heavy ORM usage (Spring Data JPA, EF Core DbContext is >50% of dependencies) | -| `SharedLibrary` | No `Dockerfile` and no executable entry point; name contains `lib`, `shared`, `common`, `sdk`, `client` | -| `WebApi` | HTTP framework detected (Spring MVC, ASP.NET Core controllers, Express, NestJS, FastAPI) and none of the above matched | -| `Unknown` | None of the above rules matched | - -**Service ID**: Lowercase slug of service name (replace spaces, dots, underscores with hyphens; remove non-alphanumeric except hyphens). Must be unique within the application. - -### Step 3: Cross-Repository Dependency Detection - -For each service, scan the following configuration and build files for references to other service names/identifiers discovered in Step 2: - -**Files to scan**: -- `application.properties`, `application.yml`, `application.yaml` (Spring Boot) -- `appsettings.json`, `appsettings.*.json` (ASP.NET Core) -- `*.env`, `.env`, `.env.*` -- `docker-compose.yml`, `docker-compose.yaml`, `docker-compose.*.yml` -- Helm chart `values.yaml`, `values.*.yaml` -- Kubernetes manifests (`*.yaml` in `k8s/`, `kubernetes/`, `deploy/`, `manifests/` directories) -- `pom.xml` (Maven dependencies — detect another service's `artifactId` as a dependency) -- `*.csproj` (NuGet `` — detect another service's project/package name) -- `package.json` (`dependencies` / `devDependencies` — detect another service's package name) - -**What to look for**: - -| DependencyType | Evidence | -|----------------|----------| -| `HttpCall` | Property value matching a pattern like `http(s)://{service-name}`, `{service-name}.default.svc`, `{service-name}:PORT`, or an environment variable named `{SERVICE_NAME}_URL` / `{SERVICE_NAME}_HOST` / `{SERVICE_NAME}_BASE_URL` | -| `MessageQueue` | Property key or value containing a topic/queue name that also appears in another service's config (e.g., `spring.kafka.topic`, `rabbitmq.queue`, `azure.servicebus.topic`) | -| `SharedLibrary` | Build file dependency whose artifact/package name matches another service's slug or name (common for `SharedLibrary`-role services) | -| `DirectReference` | `depends_on:` in docker-compose; Kubernetes service reference; direct project reference in `.sln` / `*.csproj` | - -For each detected dependency, record: -- `targetServiceId` (the other service's slug) -- `dependencyType` (from the table above) -- `evidence` (the config file path and key/value that triggered the match, e.g., `appsettings.json: ServiceUrls:OrderService = http://order-service`) - -Only record dependencies where **both** the source service and the target service are within the analyzed set. Do not record dependencies on external services (databases, cloud services, etc.) — those are covered by the `dependency-map` skill. - -### Step 4: Cycle Detection - -Perform a depth-first search (DFS) on the directed dependency graph. - -- Maintain a visited set and a recursion stack. -- When a back edge is detected (a node already in the recursion stack is encountered again), record the cycle as a warning string: - `"Circular dependency detected: {ServiceA} → {ServiceB} → ... → {ServiceA}"` -- Continue DFS after recording the cycle (do not abort). -- Collect all cycle warning strings into `cycleWarnings`. - -### Step 5: Duplicate Service Detection - -For each pair of services, check: -1. Are their slugified names identical? (e.g., `order-service` and `order-service`) -2. Do their names differ only by separator or case after normalization? (e.g., `orderservice` == `order_service` == `OrderService`) -3. Do their primary entry-point artifact names match? (e.g., same Maven ``, same .NET assembly name) - -If any condition is true, add to that service's `warnings` list: -`"WARNING: Potential duplicate of '{other-service-name}' (from {other-repo-name} at {other-repo-path}). Verify these are not the same service."` - -### Step 6: Generate Mermaid Diagram - -Build a `flowchart TD` diagram. - -**Node format**: `{ServiceId}["{ServiceName}\n({Role})"]` -- Example: `orderSvc["order-service\n(WebApi)"]` - -**Edge format** (normal dependency): -`{SourceId} -->|"{DependencyType}"| {TargetId}` -- Example: `apiGateway -->|"HttpCall"| orderSvc` - -**Cycle edge format**: -`{SourceId} -.->|"⚠ cycle"| {TargetId}` - -**Node label rules**: -- Use plain text only; avoid `@`, `#`, `$`, `%`, `&`, `<`, `>`, `"` inside node labels -- Replace double quotes in names with single quotes before embedding in labels -- All node IDs must be unique across the diagram - -**Scale limit (40-node rule)**: -- If total service count ≤ 40: show all services individually. -- If total service count > 40: collapse per-repository into aggregate nodes: - - `{repoSlug}Grp["{repoName}\n({N} services)"]` where `{repoSlug}` is the slug of `entry.repoName` - - Show only cross-repository edges between aggregate nodes. - - Add a note at the top of the diagram section: `> Note: {N} services exceed the 40-node display limit. Services are grouped by repository.` - -### Step 7: Render Output - -Produce the final output content: - -**Markdown format** : - -``` -# Topology Graph: {name} - -Analyzed {N} service(s) across {M} repositor(y/ies) for application "{name}" on {YYYY-MM-DD HH:MM UTC}. - -## Services - -```mermaid -{mermaid diagram content} -``` - -## Service Details - -| Service | Role | Language | Source Repository | Warnings | -|---------|------|----------|------------------|----------| -| {service-name} | {Role} | {language} | {repoName} | {warnings or —} | -... - -## Warnings - -- {cycle warning 1} -- {cycle warning 2} -- {duplicate warning} -- Inaccessible repository: {repoName} ({path}) (skipped) -``` -(Omit `## Warnings` section entirely when no warnings exist.) - -### Step 8: Save Output - -1. Compute the output filename: - - Full path: `{report-dir}/application-topology-graph.md` -2. Write the rendered content to the output file (create or overwrite). -3. Log: `Topology graph for '{name}' saved to {output-path}` - -## Scaling Rules - -- Maximum 40 Mermaid nodes for GitHub rendering compatibility and diagram legibility. -- When service count > 40: collapse to per-repository aggregate nodes (Step 6 scale limit rule). -- Performance expectation: analysis of 3–10 repositories should complete in under 5 minutes on a - standard developer workstation. -- For very large monorepos (>20 sub-projects), limit per-repo service detection to a maximum of - 10 services; add a note in the Warnings section: `Note: Repository '{repo}' contains more than - 10 detected services; only the first 10 are shown.` - -## Mermaid Syntax Rules - -- Use `flowchart TD` (top-down layout). -- Avoid special characters in node labels: `@`, `#`, `$`, `%`, `&`, `<`, `>`. -- Always quote arrow labels with double quotes: `-->|"label"|`. -- Use `-.->` (dotted arrow) for cycle edges with label `"⚠ cycle"`. -- All node IDs must be unique across the entire diagram. -- Do not use `subgraph` — flat node layout only (services are already grouped logically by role - via node labels). - -## Error Handling - -| Condition | Behavior | -|-----------|----------| -| All `entries` inaccessible | Abort with error message (Step 1). No output file written. | -| Subset of paths inaccessible | Continue with accessible subset. Add each inaccessible entry as `Inaccessible repository: {repoName} ({path}) (skipped)` in Warnings section. | -| Missing `repoName` or `path` field | Abort with error message (Step 1). No output file written. | -| No build files found in any repository | Best-effort: list each repo as a service with `role: Unknown` and `language: unknown`. Add note in Warnings: `Note: No recognized build files were found. Service roles could not be inferred.` | -| Unsupported project type | Set `language: unknown` and `role: Unknown` for that service. Do not fail. | -| Service name collision (duplicate IDs after slugification) | Append the `repoName` as a suffix: `{slug}-{repo-name-slug}`. | -| Output file write error | Log error: `Failed to write topology graph for '{name}' to {report-dir}/application-topology-graph.md: {error}`. Do not abort the parent process. | - -## Success Criteria - -- Output file `{report-dir}/application-topology-graph.md` is created and non-empty. -- File contains at least one service node in the Mermaid diagram. -- All inaccessible repository paths are listed in the Warnings section (none silently ignored). -- The Mermaid diagram contains no syntax errors (valid `flowchart TD` syntax). -- The Service Details table has one row per detected service. -- The Warnings section is present only when at least one warning exists. -- No unhandled exceptions propagate to the assessment pipeline. - - diff --git a/.github/skills/rulebook-create/SKILL.md b/.github/skills/rulebook-create/SKILL.md deleted file mode 100644 index b352b3bea..000000000 --- a/.github/skills/rulebook-create/SKILL.md +++ /dev/null @@ -1,89 +0,0 @@ ---- -name: rulebook-create -description: Generate or update modernization rulebook from document sources. Use this skill when the user wants to create a rulebook, sync rulebook from a document or GitHub issue, extract migration policies from architecture docs, or update existing rulebook files with new decisions. ---- - -# Rulebook Create - -Analyze source documents and generate a modernization rulebook — three markdown files that capture an organization's modernization strategy, approved migration targets, and enforceable policies (standards + guardrails). - -## User Input - -- **output-path** (Required): The folder to save the rulebook files. -- **source-file-path** (Optional): Path to the source file or directory containing the document content -- **use-defaults** (Optional): When `true`, read the built-in default rulebook files as reference to fill gaps in the generated rulebook. User source takes priority when there is a conflict. Default files: [charter-default](references/charter-default.md), [targets-default](references/targets-default.md), [policies-default](references/policies-default.md) - -## Output Structure - -``` -${output-path}/ -├── charter.md # Scope, 6R modernization strategy, and principles -├── targets.md # Approved target technologies, library mappings, and target artifacts -└── policies.md # Standards + guardrails: naming, security, compliance, prohibited/required tech, validation gates -``` - -## Principles - -- **Source Fidelity**: The rulebook is loaded and enforced by automated agents at runtime — a fabricated policy causes wrong migration decisions. Only include content explicitly present in the source document or user prompt. If a category isn't mentioned, omit that section entirely rather than adding placeholders. -- **Incremental Merge**: When output files already exist, merge at the **section level** — update sections with new or changed content, preserve unchanged sections verbatim. If the source explicitly removes or contradicts an existing entry, update that entry. Never drop existing content simply because the source is silent on it. -- **Direct Policy Output**: State policies as-is. Do not include rationale, explanations, or implementation guidance — those belong elsewhere (skills). - -## Classification Principles - -Each file serves a distinct purpose and is consumed at different stages of the modernization workflow: - -- **charter.md** — Rulebook metadata (name, version), scope (covered applications, languages constraints), 6R modernization strategy (Rehost / Replatform / Refactor decision table with override conditions — Retire, Retain, Repurchase are outside app modernization scope), and guiding principles. Loaded during assessment and plan creation to determine what to modernize and what strategy to apply. -- **targets.md** — Approved framework versions, compute/data/integration services, source-to-target library mappings, and target artifacts (container base images). Loaded during assessment (to define target services to assess against) and plan creation (to determine what tasks to generate). -- **policies.md** — Naming & metadata standards, security requirements, compliance requirements, guardrails (prohibited technologies/patterns, required elements, region constraints), validation & quality gates, and coding style guidelines. Loaded during assessment, plan creation, and execution to enforce standards and guardrails. - -## Workflow - -### Step 1: Read and Analyze Source - -1. If ${source-file-path} is provided, read the content (may be a GitHub issue export, markdown file, or a directory of files — if a directory, read all files recursively) -2. If ${use-defaults} is `true`, read the default rulebook files ([charter-default](references/charter-default.md), [targets-default](references/targets-default.md), [policies-default](references/policies-default.md)) as reference context. When the user source and a default conflict, the user source takes priority. -3. Check if output files already exist in ${output-path} — read them for merge comparison -4. Classify each decision from the source (and/or the user prompt) into one of the three output files using the Classification Guide above - -If no source file is provided, work with the user prompt and any existing rulebook files only. - -### Step 2: Generate Rulebook Files - -For each file, use the corresponding template as the structural reference, then fill in content extracted from the source. -Only include sections/subsections that have meaningful, source-backed content. Do not emit empty headings, empty tables, or placeholder-only sections. - -#### charter.md - -Use the template [charter-template](charter-template.md) as a section catalog: -- Metadata, Scope, 6R Modernization Strategy (Rehost / Replatform / Refactor), Principles - -#### targets.md - -Use the template [targets-template](targets-template.md) as a section catalog: -- Target Frameworks, Target Compute Services, Target Data Services, Target Integration Services, Target Libraries, Target Artifacts - -#### policies.md - -Use the template [policies-template](policies-template.md) as a section catalog: -- Naming & Metadata Standards, Security Requirements, Compliance Requirements, Guardrails (Hard Boundaries), Validation & Quality Gates, Coding Style Guidelines - -For each file: if it already exists, merge new content; if not, create it fresh. - -### Step 3: Validate - -Verify the output before finishing: -- [ ] All three files exist in ${output-path} -- [ ] Each file includes only sections with meaningful content from the source -- [ ] No empty headings, empty tables, or placeholder-only sections remain -- [ ] Every decision in the source document is reflected in at least one output file -- [ ] No content was invented beyond what the source provides - -### Step 4: Present Summary - -Report to the user: -- Number of target technologies defined -- Number of library migration mappings captured -- Number of prohibited technologies/patterns -- Number of required elements -- Number of validation/quality gates -- Sections omitted due to missing source content — flag these as gaps for architect review diff --git a/.github/skills/rulebook-create/charter-template.md b/.github/skills/rulebook-create/charter-template.md deleted file mode 100644 index e91281fee..000000000 --- a/.github/skills/rulebook-create/charter-template.md +++ /dev/null @@ -1,36 +0,0 @@ -# Charter - -## Metadata - -| Field | Value | -|-------|-------| -| Rulebook Name | | -| Version | | -| Changelog | | - -## Scope - -### Covered Applications and Languages - -Only .NET and Java applications are in scope. - -### Application Types - -**Included:** - -**Excluded:** - -### Custom Libraries - -### Constraints - -## Modernization Strategy (6R Guidelines) - -Of the 6R strategies, this rulebook covers **Rehost**, **Replatform**, and **Refactor** — the three that involve app modernization. Retire, Retain, and Repurchase are outside the scope of app modernization. - -Supported strategies: **Rehost** (lift-and-shift, no code changes), **Replatform** (minimal code changes — containerize, adopt managed services), **Refactor** (modify code/architecture — decompose, upgrade). - -| Application Type | Default Strategy | Override Conditions | -|------------------|-----------------|---------------------| - -## Principles diff --git a/.github/skills/rulebook-create/policies-template.md b/.github/skills/rulebook-create/policies-template.md deleted file mode 100644 index c41acce73..000000000 --- a/.github/skills/rulebook-create/policies-template.md +++ /dev/null @@ -1,70 +0,0 @@ -# Policies - -Enforceable standards and hard boundaries for .NET and Java modernization. Every policy here is validatable against generated artifacts. - -## Naming & Metadata Standards - -### Resource Naming Patterns - -| Resource Type | Pattern | Example | -|--------------|---------|---------| - -### Tagging Requirements - -| Tag | Required | Description | -|-----|----------|-------------| - -## Security Requirements - -### Authentication & Authorization - -### Secrets Management - -### Network Security - -### Encryption - -## Compliance Requirements - -### Applicable Frameworks - -| Framework | Key Constraints | -|-----------|----------------| - -### Data Classification - -## Guardrails (Hard Boundaries) - -### Prohibited Technologies - -| Technology | Reason | Approved Alternative | -|-----------|--------|---------------------| - -### Prohibited Patterns - -| Pattern | Reason | Approved Alternative | -|---------|--------|---------------------| - -### Required Elements - -Every modernized application must include: - -#### Cloud Resources - -#### Monitoring - -### Approved Regions / Residency Constraints - -## Validation & Quality Gates - -### Required Scanners/Tools - -### Pipeline Gates - -### Confidence Thresholds - -## Coding Style Guidelines - -### .NET - -### Java diff --git a/.github/skills/rulebook-create/references/charter-default.md b/.github/skills/rulebook-create/references/charter-default.md deleted file mode 100644 index 49084775a..000000000 --- a/.github/skills/rulebook-create/references/charter-default.md +++ /dev/null @@ -1,59 +0,0 @@ -# Charter — Default Modernization Rulebook - -## Metadata - -- **Name:** `azure-app-modernization-default` -- **Description:** Default rulebook for modernizing .NET and Java applications to Azure App Service, Container Apps, or AKS - ---- - -## Scope - -**Included:** Java (8+), .NET Framework / .NET 6+ — web apps, REST/gRPC APIs, background workers, event-driven microservices. Custom libraries used by in-scope apps are in-scope. - -**Excluded:** Mainframe/COBOL, desktop apps (WinForms/WPF/Electron), embedded/IoT, standalone database migrations, standalone data pipelines/ETL, Node.js and Python applications. - -**Constraints:** - -| Constraint | Rule | -|---|---| -| Traffic | Production apps require zero-downtime migration (blue-green, canary) | -| Timelines | Target 90-day execution cycles per wave | -| Compliance | Regulated apps (PCI-DSS, HIPAA, FedRAMP) must preserve compliance posture | - ---- - -## Modernization Strategy (6R Guidelines) - -Of the 6R strategies, this rulebook covers **Rehost**, **Replatform**, and **Refactor** — the three that involve app modernization. Retire, Retain, and Repurchase are outside the scope of app modernization. - -Supported strategies: **Rehost** (lift-and-shift, no code changes), **Replatform** (minimal code changes — containerize, adopt managed services), **Refactor** (modify code/architecture — decompose, upgrade). - -| App Type | Strategy | Target | Override | -|---|---|---|---| -| Stateless web apps / APIs (containerized) | Replatform | Container Apps | Complex orchestration → AKS | -| Stateless web apps / APIs (non-containerized) | Replatform | App Service | — | -| Apps with OS-level dependencies (COM, registry, custom installers) | Rehost | App Service Managed Instance | Can containerize → Container Apps or AKS | -| Stateful monoliths | Refactor | Container Apps + backing services | Cannot decompose in timeline → App Service | -| Legacy (EOL framework) | Refactor | Container Apps on supported runtime | OS dependencies → App Service MI | -| Background workers / jobs | Replatform | Container Apps | Complex orchestration → AKS | -| Event-driven / messaging | Replatform | Container Apps + Service Bus | Complex orchestration → AKS | - -**Decision sequence:** Match app to table row → apply default unless override met → escalate to architecture review if no match. - ---- - -## Principles - -| # | Principle | Rule | -|---|---|---| -| 1 | Cloud-first | Target Azure PaaS by default; IaaS only when required | -| 2 | Zero data loss | Validated data migration and rollback plan before cutover | -| 3 | Reversibility | Prefer reversible steps; no one-way doors without sign-off | -| 4 | Least privilege | Azure RBAC + managed identities; no shared credentials | -| 5 | Infrastructure as code | All infra in Bicep or Terraform; no portal-only resources | -| 6 | Observability | Structured logs, metrics, distributed traces via Azure Monitor | -| 7 | Managed services | Managed databases, caches, brokers over self-hosted | -| 8 | Minimize blast radius | Small independent waves; resource group isolation | -| 9 | Cost awareness | Right-size resources; consumption SKUs; tag for cost attribution | -| 10 | Security by design | Defender for Cloud, TLS 1.2+, Entra ID authentication | diff --git a/.github/skills/rulebook-create/references/policies-default.md b/.github/skills/rulebook-create/references/policies-default.md deleted file mode 100644 index 1ba737e54..000000000 --- a/.github/skills/rulebook-create/references/policies-default.md +++ /dev/null @@ -1,123 +0,0 @@ -# Policies — Default Modernization Rulebook - -Enforceable standards and hard boundaries for .NET and Java modernization. Every policy here is validatable against generated artifacts. - ---- - -## Naming & Metadata Standards - -**Pattern:** `{env}-{app}-{service}-{resourcetype}` -Example: `prod-contoso-api-app`, `prod-contoso-shared-kv` - -| Segment | Values | -|---|---| -| `{env}` | `dev`, `stg`, `prod` | -| `{app}` | Workload identifier | -| `{service}` | Component name (`api`, `web`) | -| `{resourcetype}` | CAF abbreviation (`rg`, `app`, `kv`) | - -**Required tags** on every resource: - -| Tag | Example | -|---|---| -| `environment` | `dev`, `staging`, `production` | -| `owner` | `platform-team` | -| `cost-center` | `CC-4200` | -| `workload` | `contoso-orders` | -| `migration-wave` | `wave-2` | - ---- - -## Security Requirements - -**Identity:** Managed Identity required for all service-to-service auth. Use `DefaultAzureCredential`. Connection strings with embedded keys (`AccountKey=`, `SharedAccessKey=`) are prohibited. Service principal secrets permitted only for CI/CD with 90-day rotation. - -**Secrets:** Azure Key Vault required. No secrets in source, config files, or env vars. Key Vault access via RBAC (Secrets User / Crypto User), not access policies. - -**Network:** Private endpoints for all data services. NSGs on every subnet. No public IPs on workloads — route through App Gateway, Front Door, or APIM. TLS 1.2+ enforced; TLS 1.0/1.1 prohibited. - -**Encryption:** At rest — platform-managed keys default; CMK via Key Vault for confidential/regulated data. In transit — TLS 1.2+ mandatory. - ---- - -## Compliance Requirements - -**Baseline:** WAF and CIS Azure Foundations Benchmark. - -**Data classification:** - -| Level | Applies | Controls | -|---|---|---| -| General | Default | Standard encryption | -| Confidential | PII, financial, health | CMK, audit logging, restricted access | - -**Data residency:** Approved regions only (see Guardrails). No cross-region replication without override. - -**Audit logging:** Diagnostic settings on all data-plane operations. Central Log Analytics workspace. 90-day online retention, 1-year archive. - ---- - -## Guardrails (Hard Boundaries) - -### Prohibited Technologies - -| Technology | Reason | -|---|---| -| FTP / FTPS | Insecure; use managed storage | -| Basic authentication | Credential exposure | -| Self-signed certs (prod) | Trust chain violation | -| Legacy Azure SDKs (Track 1) | Deprecated | -| .NET Framework < 4.8 | EOL | -| Java < 11 | EOL | -| log4j 1.x | EOL, multiple known vulnerabilities | - -### Prohibited Patterns - -| Pattern | Reason | -|---|---| -| Hardcoded secrets/credentials | Security violation | -| Embedded connection keys | Use Managed Identity | -| `SELECT *` | Performance/coupling risk | - -### Required Elements - -Every generated service code must include: -- `/health` (liveness) and `/ready` (readiness) endpoints -- OpenTelemetry or Azure Monitor structured logging -- Graceful shutdown (SIGTERM / cancellation token) -- Retry with exponential backoff + jitter for external calls - -### Approved Regions - -`eastus`, `eastus2`, `westus2`, `westeurope`, `northeurope` — override per org as needed. - ---- - -## Validation & Quality Gates - -**Required scanners:** Container image scanning (Trivy or equivalent), dependency vulnerability scanning (Dependabot or equivalent). - -**Pipeline gates:** Build, unit tests, code analysis, dependency scan, container scan, staging deployment, smoke tests, production deployment with approval gate. Minimum 80% code coverage. - -**Confidence thresholds:** No High or Critical findings in dependency scan before production deployment. - ---- - -## Coding Style Guidelines - -General principles: - -- `.editorconfig` required; generate from framework conventions if missing -- Descriptive names, no abbreviations unless domain-standard -- Prefer async/non-blocking patterns; 120-char soft line limit -- 4-space indent, LF line endings - -### .NET -- Sync-over-async (`Task.Result`, `.GetAwaiter().GetResult()`) — deadlock risk -- `new HttpClient()` — socket exhaustion; use `IHttpClientFactory` -- `Thread.Sleep` in async paths — blocks threadpool; use `Task.Delay` -- Catch-all `catch(Exception)` without rethrow — swallows errors - -### Java -- Blocking I/O in reactive pipelines — use `CompletableFuture` or reactive alternatives -- Catch-all `catch(Exception)` without rethrow — swallows errors diff --git a/.github/skills/rulebook-create/references/targets-default.md b/.github/skills/rulebook-create/references/targets-default.md deleted file mode 100644 index 9ebffd38e..000000000 --- a/.github/skills/rulebook-create/references/targets-default.md +++ /dev/null @@ -1,73 +0,0 @@ -# Targets — Default Modernization Rulebook - -Approved target technologies for .NET and Java modernization. Defines *what is approved* — not implementation steps (those belong in skills). - ---- - -## Target Frameworks - -| Language | Target Version | EOL Versions (must upgrade) | -|---|---|---| -| Java | 21+ LTS, Spring Boot 3.x | Java 8 EOL; 11/17 supported but 21+ preferred | -| .NET | 8+ LTS | .NET Framework 4.x, .NET Core 3.1, .NET 6 end-of-support | - ---- - -## Target Compute Services - -| Workload Type | Target | Selection Rule | -|---|---|---| -| Containerized stateless apps / APIs | **Azure Container Apps** | Scale-to-zero, managed ingress, Dapr/KEDA; no full K8s control plane needed | -| Complex orchestration, multi-container | **Azure Kubernetes Service (AKS)** | Full K8s control plane, custom networking, service mesh, DaemonSets | -| Non-containerized web apps | **Azure App Service** | Platform-managed TLS, deployment slots, integrated auth; lowest migration friction | -| Apps with OS-level dependencies | **Azure App Service Managed Instance** | COM, registry, custom installers, RDP access via Bastion; lift-and-improve with OS control | - -**Selection:** Evaluate workload characteristics against table → select matching target. No single universal default; target is determined by app profile. - ---- - -## Target Data Services - -Backing services for modernized applications — not standalone migration targets. - -| Source | Target | When | -|---|---|---| -| SQL Server | **Azure SQL Database** | Default; Managed Instance only if instance-scoped features needed | -| PostgreSQL | **Azure Database for PostgreSQL Flexible Server** | Default for PostgreSQL | -| MySQL | **Azure Database for MySQL Flexible Server** | Default for MySQL | -| Oracle, other RDBMS | Evaluate case-by-case | Prefer Azure SQL or PostgreSQL | -| MongoDB, DynamoDB, document stores | **Azure Cosmos DB** | Multi-model NoSQL; select appropriate API | -| Memcached, self-hosted Redis, in-process caches | **Azure Cache for Redis** | Distributed caching and session state | -| File shares, NFS, local disk | **Azure Blob Storage** | Unstructured data; Hot/Cool/Archive tiers | -| SMB file shares | **Azure Files** | When SMB/NFS mount semantics required | - ---- - -## Target Integration Services - -Backing services — selected when the application's messaging/eventing/API layer migrates alongside the app. - -| Source | Target | When | -|---|---|---| -| RabbitMQ, MSMQ, ActiveMQ, IBM MQ | **Azure Service Bus** | Reliable enterprise messaging (queues, topics, sessions, dead-letter) | -| Custom webhooks, polling | **Azure Event Grid** | Reactive event-driven, push delivery | -| Kafka, high-throughput streaming | **Azure Event Hubs** | High-throughput ingestion; Kafka protocol compatible | -| Custom reverse proxies, legacy gateways | **Azure API Management** | API facade, rate limiting, auth, developer portal | - ---- - -## Target Libraries - -Source → target mappings for .NET and Java task generation. - -| Category | Source | Target | -|---|---|---| -| Azure SDK (.NET) | `Microsoft.Azure.*` (Track 1) | `Azure.*` (Track 2) | -| Azure SDK (Java) | `com.microsoft.azure:*` | `com.azure:*` | -| Logging (.NET) | log4net, NLog | OpenTelemetry + Azure Monitor | -| Logging (Java) | log4j 1.x, SLF4J + Logback | OpenTelemetry + Azure Monitor | -| Auth | Connection strings, hardcoded keys | `DefaultAzureCredential` (Managed Identity) | -| Config (.NET) | `web.config`, `app.config`, hardcoded | Azure App Configuration + Key Vault | -| Config (Java) | `application.properties`, hardcoded | Azure App Configuration + Key Vault | -| DI (.NET) | Autofac, Unity, Ninject | Built-in `Microsoft.Extensions.DependencyInjection` | -| HTTP (.NET) | Raw `HttpClient`, `WebClient`, `RestSharp` | `IHttpClientFactory` with resilience policies | diff --git a/.github/skills/rulebook-create/targets-template.md b/.github/skills/rulebook-create/targets-template.md deleted file mode 100644 index 0ac0dd15d..000000000 --- a/.github/skills/rulebook-create/targets-template.md +++ /dev/null @@ -1,35 +0,0 @@ -# Targets - -Approved target technologies for .NET and Java modernization. Defines *what is approved* — not implementation steps. - -## Target Frameworks - -| Language | Target Version | Notes | -|----------|---------------|-------| - -## Target Compute Services - -| Platform | Use When | -|----------|----------| - -## Target Data Services - -| Service | Use When | -|---------|----------| - -## Target Integration Services - -| Service | Use When | -|---------|----------| - -## Target Libraries - -Source → target mappings for .NET and Java. - -| Category | Source | Target | Notes | -|----------|--------|--------|-------| - -## Target Artifacts - -| Artifact | Location | Notes | -|----------|----------|-------| diff --git a/.github/skills/security-assessment-merge/SKILL.md b/.github/skills/security-assessment-merge/SKILL.md deleted file mode 100644 index da58db577..000000000 --- a/.github/skills/security-assessment-merge/SKILL.md +++ /dev/null @@ -1,318 +0,0 @@ ---- -name: security-assessment-merge -description: Merge CVE and CWE security assessment results into a unified security report and update report.json ---- - -# Security Assessment: Merge and Report - -## Role - -You are a **security assessment aggregator**. Your task is to read all CVE and CWE result files produced by earlier security skills, merge them into a unified security assessment report, and inject the findings into `report.json`. - -> **Important:** You are a data aggregator, NOT an analyzer. Do NOT re-scan the codebase. Your sole responsibility is to read, normalize, merge, and write the security findings that were already produced by the CVE and CWE assessment skills. - -## Working Directory - -All security assessment input and output files are located under: - -``` -.github/modernize/assessment/engines/security/ -``` - -All file paths in this skill are relative to this directory unless otherwise noted. - -## Objective - -1. Read all security assessment result files from the `.github/modernize/assessment/engines/security/` directory -2. Normalize CVE and CWE findings into a unified format -3. Generate `security-assessment.json` (machine-readable merged report) in the same directory -4. Generate `security-assessment.md` (human-readable markdown report) in the same directory -5. Inject a `"security"` array into the existing `report.json` - -## Instructions - -### Step 1: Read CVE Results - -Read the CVE result file at `.github/modernize/assessment/engines/security/cve-assessment-result.json`. - -This file is a **flat JSON array** where each element has: -```json -{ - "id": "CVE-2024-xxxx", - "name": "Summary", - "status": "FOUND", - "category": "CVE", - "severity": "critical|high|medium|low", - "storyPoint": 1, - "evidence": { - "files": ["pom.xml:42"], - "explanation": "Markdown description..." - } -} -``` - -Only include entries where `status` is `"FOUND"`. - -**Filter by minimum CVE severity:** The minimum severity threshold is provided in the assessment prompt instructions. Only include CVE findings whose severity meets or exceeds the specified threshold. The severity order from lowest to highest is: `low` < `medium` < `high` < `critical`. Exclude findings with unknown or missing severity. If no threshold was specified in the prompt, default to `high`. - -If the file does not exist or is empty, treat CVE findings as an empty list. - -### Step 2: Read CWE Results - -Read all CWE result files matching `.github/modernize/assessment/engines/security/result-cwe-*.json`. - -Each CWE file uses a **wrapper format**: -```json -{ - "input_name": "CWE - Category Name", - "status": "success", - "result": { - "values": [ - { - "id": "CWE-79", - "name": "Cross-site Scripting", - "status": "FOUND", - "category": "Injection Attacks", - "severity": "optional", - "storyPoint": 8, - "description": "The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.", - "evidence": { - "files": ["src/Controller.java"], - "explanation": "Description..." - } - } - ] - } -} -``` - -Navigate to `result.values` in each file and extract entries where `status` is `"FOUND"`. - -If no CWE files exist, treat CWE findings as an empty list. - -### Step 3: Build the Unified Security Findings Array - -Transform all FOUND CVE and CWE items into the unified format: - -```json -{ - "id": "CVE-2024-xxxx or CWE-79", - "title": "Human-readable title", - "category": "CVE or CWE category (e.g., Injection Attacks)", - "severity": "mandatory|optional|potential", - "description": "Detailed description", - "evidence": { - "files": ["path/to/file.java"], - "explanation": "Why this was flagged" - }, - "storyPoint": 1 -} -``` - -For **CVE findings**: Map `name` → `title`, use `explanation` as both `description` and `evidence.explanation`. **Map the raw CVE severity to assessment severity levels:** -- `critical` or `high` → `mandatory` -- `medium` → `optional` -- `low` or unknown → `potential` - -For **CWE findings**: Map `name` → `title`, map `severity` → `severity` (already in assessment format), map `storyPoint` → `storyPoint`, map `description` → `description`, use `explanation` as `evidence.explanation`. - -### Step 4: Calculate Summary Statistics - -Count: -- `totalFindings`: Total number of FOUND CVE + CWE items -- `cveCount`: Number of CVE findings -- `cweCount`: Number of CWE findings -- `bySeverity`: Count of findings grouped by severity (`mandatory`, `optional`, `potential`) -- `byCategory`: Count of findings grouped by category (`CVE`, `Injection Attacks`, etc.) -- `totalRulesAssessed`: Total number of CWE checklist items across all `.github/modernize/assessment/engines/security/result-cwe-*.json` files (both FOUND and NOT_FOUND) -- `rulesPassed`: `totalRulesAssessed - cweCount` - -### Step 5: Write security-assessment.json - -Write the full merged report to `.github/modernize/assessment/engines/security/security-assessment.json`: - -```json -{ - "GeneratedAt": "2026-04-09T12:00:00.0000000Z", - "ProjectPath": ".", - "Summary": { - "TotalFindings": 5, - "CveCount": 2, - "CweCount": 3, - "BySeverity": { "mandatory": 3, "optional": 1, "potential": 1 }, - "ByCategory": { "CVE": 2, "Injection Attacks": 2, "Code Quality": 1 }, - "TotalRulesAssessed": 59, - "RulesPassed": 56 - }, - "CveFindings": [ - { - "Id": "CVE-2024-xxxx", - "Name": "Vulnerability Title", - "Category": "CVE", - "Severity": "mandatory", - "StoryPoint": 1, - "Description": "...", - "Files": ["pom.xml:42"], - "Explanation": "..." - } - ], - "CweFindings": [ - { - "Id": "CWE-79", - "Name": "Cross-site Scripting", - "Category": "Injection Attacks", - "Severity": "optional", - "StoryPoint": 8, - "Description": "...", - "Files": ["src/Controller.java"], - "Explanation": "..." - } - ] -} -``` - -### Step 6: Write security-assessment.md - -Write a human-readable markdown report to `.github/modernize/assessment/engines/security/security-assessment.md`: - -```markdown -# Security Assessment Report - -**Generated:** - -## Summary - -| Metric | Count | -|--------|-------| -| Total Findings | N | -| CVE Vulnerabilities | N | -| CWE Vulnerabilities | N | -| Total Rules Assessed | N | -| Rules Passed | N | - -### By Severity - -| Severity | Count | -|----------|-------| -| mandatory | N | -| optional | N | -| potential | N | - -## CVE Findings (Dependency Vulnerabilities) - -### CVE-2024-xxxx: Title -- **Severity:** mandatory -- **Story Points:** 1 -- **Files:** pom.xml:42 - - - -## CWE Findings (Code-Level Vulnerabilities) - -### CWE-79: Cross-site Scripting -- **Category:** Injection Attacks -- **Severity:** optional -- **Story Points:** 8 -- **Files:** src/Controller.java - - -``` - -If there are no findings at all, write: -```markdown -## No security vulnerabilities found. - -The assessment did not detect any CVE or CWE vulnerabilities in the codebase. -``` - -### Step 7: Merge into report.json - -The `report.json` file lives in a versioned report directory: - -``` -.github/modernize/assessment/reports/report-{reportId}/report.json -``` - -where `{reportId}` is a `yyyyMMddHHmmss` timestamp (e.g., `20260410120000`). This directory is created by the main assessment skill. - -**Step 7a: Locate report.json** - -Find the latest versioned report directory by listing directories matching the `report-*` pattern and sorting in descending order (newest first): - -```bash -REPORTS_DIR=".github/modernize/assessment/reports" -REPORT_DIR=$(ls -d "$REPORTS_DIR"/report-* 2>/dev/null | sort -r | head -n 1) - -if [ -n "$REPORT_DIR" ] && [ -f "$REPORT_DIR/report.json" ]; then - echo "Found report.json at: $REPORT_DIR/report.json" -else - echo "Warning: No versioned report directory found under $REPORTS_DIR" -fi -``` - -**Step 7b: Merge security findings into report.json** - -**Use `jq` for safe JSON manipulation** — do NOT manually rewrite report.json. - -Write the unified findings array to a temporary file, then merge: - -```bash -SECURITY_DIR=".github/modernize/assessment/engines/security" - -# Write the unified findings array -cat > "$SECURITY_DIR/unified-findings.json" << 'SECURITY_EOF' -[ - ... unified findings array from Step 3 ... -] -SECURITY_EOF - -# Merge into report.json using jq -if [ -n "$REPORT_DIR" ] && [ -f "$REPORT_DIR/report.json" ] && command -v jq &> /dev/null; then - jq --argjson sec "$(cat "$SECURITY_DIR/unified-findings.json")" '. + {"security": $sec}' "$REPORT_DIR/report.json" > "$REPORT_DIR/report.json.tmp" && mv "$REPORT_DIR/report.json.tmp" "$REPORT_DIR/report.json" -elif [ -n "$REPORT_DIR" ] && [ -f "$REPORT_DIR/report.json" ]; then - # Fallback without jq: use python - python3 -c " -import json -report_path = '$REPORT_DIR/report.json' -with open(report_path, 'r') as f: report = json.load(f) -with open('$SECURITY_DIR/unified-findings.json', 'r') as f: security = json.load(f) -report['security'] = security -with open(report_path, 'w') as f: json.dump(report, f, indent=2) -" 2>/dev/null || echo "Warning: Could not merge security into report.json (jq and python3 unavailable)" -else - echo "Warning: report.json not found — skipping merge. The standalone security-assessment.json is still valid." -fi - -# Clean up temp file -rm -f "$SECURITY_DIR/unified-findings.json" -``` - -**Step 7c: Handle missing report.json** - -If no `report-*` directory or `report.json` exists (e.g., AppCAT was not run or failed), create a minimal stub in a new versioned directory: - -```bash -if [ -z "$REPORT_DIR" ] || [ ! -f "$REPORT_DIR/report.json" ]; then - REPORT_ID=$(date -u +"%Y%m%d%H%M%S") - REPORT_DIR="$REPORTS_DIR/report-$REPORT_ID" - mkdir -p "$REPORT_DIR" - cat > "$REPORT_DIR/report.json" << STUB_EOF -{ - "metadata": { - "id": "$REPORT_ID", - "generated_at": "$(date -u +"%Y-%m-%dT%H:%M:%S.0000000Z")", - "note": "Security-only report (AppCAT was not run or failed)" - }, - "security": $(cat "$SECURITY_DIR/unified-findings.json" 2>/dev/null || echo "[]") -} -STUB_EOF - echo "Created stub report at: $REPORT_DIR/report.json" -fi -``` - -## Error Handling - -- If no security result files exist at all, write empty reports (`security-assessment.json` with zero findings, `security-assessment.md` with "No vulnerabilities found") -- If a CWE result file is malformed, skip it and continue with the rest -- If the `report.json` merge fails, log a warning but do NOT fail the overall task — the standalone `security-assessment.json` is still valid -- Always produce all three output files (`security-assessment.json`, `security-assessment.md`, and the merged `report.json`) diff --git a/.github/skills/validate-assessment-rulebook-compliance/SKILL.md b/.github/skills/validate-assessment-rulebook-compliance/SKILL.md deleted file mode 100644 index 2e03e2581..000000000 --- a/.github/skills/validate-assessment-rulebook-compliance/SKILL.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -name: validate-assessment-rulebook-compliance -description: Validate that assessment findings cover rulebook source technologies and patterns ---- - -# Validate Assessment Rulebook Compliance - -Check whether the assessment detected the source technologies and patterns defined in the rulebook. - -## User Input - -- assessment-findings-path (Mandatory): Path to the pre-processed assessment findings JSON file -- compliance-output-path (Mandatory): Path to write the compliance markdown summary - -## Workflow - -1. Read the assessment findings from ${assessment-findings-path}. This is a JSON array of objects with `ruleId`, `title`, and `description` fields representing issues detected during assessment. -2. Read the rulebook files provided as attachments (targets.md, policies.md). -3. Extract each source technology, pattern, or constraint mentioned in the rulebook files. -4. For each rulebook item, determine whether the assessment findings contain a rule that detects or covers it. Use semantic matching — the rule description does not need to be an exact match, but must clearly relate to the same technology or pattern. -5. Write a markdown summary to ${compliance-output-path} with the following format: - -## Assessment Rulebook Coverage - -| Rulebook | Source Technology/Pattern | Status | Evidence (Rule ID) | -|----------|--------------------------|--------|-------------------| -| targets.md | brief technology/pattern summary | ✅ DETECTED | matching-rule-id | -| policies.md | another pattern | ❌ NOT DETECTED | - | - -**DETECTED: X/Y · NOT DETECTED: Z/Y** - -## Rules - -- Only include source technologies and patterns from the rulebook — do not include target recommendations. -- A rulebook item is DETECTED if at least one assessment rule clearly relates to detecting that technology or pattern. -- If multiple rules match, list the most specific one. -- Only write the markdown file, do not modify any other files. diff --git a/.github/skills/validate-rulebook-compliance/SKILL.md b/.github/skills/validate-rulebook-compliance/SKILL.md deleted file mode 100644 index 3b64f635d..000000000 --- a/.github/skills/validate-rulebook-compliance/SKILL.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -name: validate-rulebook-compliance -description: Validate rulebook compliance by mapping rulebook rules to plan tasks ---- - -# Validate Rulebook Compliance - -Map each rulebook rule to the tasks in the modernization plan and produce a compliance summary. - -## User Input - -- tasks-json-path (Mandatory): Path to the tasks.json file -- compliance-output-path (Mandatory): Path to write the compliance markdown summary - -## Workflow - -1. Read the tasks from ${tasks-json-path} -2. Read the rulebook files provided as attachments -3. For each rulebook rule, determine which task (if any) addresses it -4. Write a markdown summary to ${compliance-output-path} with the following format: - -## Rulebook Compliance - -| Rulebook | Rule | Status | Task | -|----------|------|--------|------| -| targets.md | brief rule summary | ✅ COVERED | 001 | -| policies.md | another rule | ❌ NOT COVERED | - | - -**COVERED: X/Y · NOT COVERED: Z/Y** - -Include all rules from all rulebook files. Only write the markdown file, do not modify any other files. diff --git a/.github/skills/validate-rulebook-evidence/SKILL.md b/.github/skills/validate-rulebook-evidence/SKILL.md deleted file mode 100644 index 624576d69..000000000 --- a/.github/skills/validate-rulebook-evidence/SKILL.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -name: validate-rulebook-evidence -description: Analyze code changes and produce rulebook compliance evidence from git diff ---- - -# Validate Rulebook Evidence - -Analyze the git diff and produce rulebook compliance evidence showing which rules were implemented. - -## User Input - -- baseline-commit-sha (Mandatory): The baseline commit SHA to diff from -- rulebook-file-list (Mandatory): Comma-separated list of rulebook file names -- evidence-output-path (Mandatory): Path to write the evidence markdown summary - -## Workflow - -1. Analyze the git diff from baseline commit ${baseline-commit-sha} to HEAD -2. The rulebook files are: ${rulebook-file-list} -3. Write a markdown summary to ${evidence-output-path} with the following format: - -## Rulebook Code Evidence (from diff) - -### [policies.md] -**Rule: brief rule summary** -```diff -+ filepath:line added code -- filepath:line removed code -``` - -Focus on the 2-5 most impactful rules. Each diff line starts with '+' or '-' and includes file:line prefix. -Only write the markdown file, do not modify any other files. From 4cfac9dd27de752393c88d55bd9711054c9161c2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 May 2026 06:19:41 +0000 Subject: [PATCH 4/7] Fix generated assessment HTML formatting Agent-Logs-Url: https://github.com/qianwens/PhotoAlbum-Java/sessions/82480c71-f7c9-403f-bbf2-d2fbf546a091 Co-authored-by: qianwens <37290631+qianwens@users.noreply.github.com> --- .../assessment/reports/report-20260521061104/report.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/modernize/assessment/reports/report-20260521061104/report.html b/.github/modernize/assessment/reports/report-20260521061104/report.html index 8d1f35bfc..5ae36be39 100644 --- a/.github/modernize/assessment/reports/report-20260521061104/report.html +++ b/.github/modernize/assessment/reports/report-20260521061104/report.html @@ -388,7 +388,7 @@

Explanation

Explanation

-

The application is setting the server port. To migrate a Java application that sets the server port to Azure Container Apps:

*Azure Container Apps allows you to expose port according to your Azure Container Apps resource configuration. For instance, a Spring Boot application listens to port of 8080 by default, but it can be set with server.port or environment variable SERVER_PORT as you need.

+

The application is setting the server port. To migrate a Java application that sets the server port to Azure Container Apps:

Azure Container Apps allows you to expose a port according to your Azure Container Apps resource configuration. For instance, a Spring Boot application listens on port 8080 by default, but it can also be set with `server.port` or the `SERVER_PORT` environment variable as needed.

From 4baac2749fdc07b14bacdbe2ccc5f29133d0dbf5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 May 2026 06:20:28 +0000 Subject: [PATCH 5/7] Normalize assessment HTML code formatting Agent-Logs-Url: https://github.com/qianwens/PhotoAlbum-Java/sessions/82480c71-f7c9-403f-bbf2-d2fbf546a091 Co-authored-by: qianwens <37290631+qianwens@users.noreply.github.com> --- .../assessment/reports/report-20260521061104/report.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/modernize/assessment/reports/report-20260521061104/report.html b/.github/modernize/assessment/reports/report-20260521061104/report.html index 5ae36be39..780e62142 100644 --- a/.github/modernize/assessment/reports/report-20260521061104/report.html +++ b/.github/modernize/assessment/reports/report-20260521061104/report.html @@ -388,7 +388,7 @@

Explanation

Explanation

-

The application is setting the server port. To migrate a Java application that sets the server port to Azure Container Apps:

Azure Container Apps allows you to expose a port according to your Azure Container Apps resource configuration. For instance, a Spring Boot application listens on port 8080 by default, but it can also be set with `server.port` or the `SERVER_PORT` environment variable as needed.

+

The application is setting the server port. To migrate a Java application that sets the server port to Azure Container Apps:

Azure Container Apps allows you to expose a port according to your Azure Container Apps resource configuration. For instance, a Spring Boot application listens on port 8080 by default, but it can also be set with server.port or the SERVER_PORT environment variable as needed.

From 4a34e6b67f9836beeb40f7c47ed5f5972a64a2f2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 May 2026 06:21:16 +0000 Subject: [PATCH 6/7] Refine assessment HTML wording Agent-Logs-Url: https://github.com/qianwens/PhotoAlbum-Java/sessions/82480c71-f7c9-403f-bbf2-d2fbf546a091 Co-authored-by: qianwens <37290631+qianwens@users.noreply.github.com> --- .../assessment/reports/report-20260521061104/report.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/modernize/assessment/reports/report-20260521061104/report.html b/.github/modernize/assessment/reports/report-20260521061104/report.html index 780e62142..e934f2e4e 100644 --- a/.github/modernize/assessment/reports/report-20260521061104/report.html +++ b/.github/modernize/assessment/reports/report-20260521061104/report.html @@ -388,7 +388,7 @@

Explanation

Explanation

-

The application is setting the server port. To migrate a Java application that sets the server port to Azure Container Apps:

Azure Container Apps allows you to expose a port according to your Azure Container Apps resource configuration. For instance, a Spring Boot application listens on port 8080 by default, but it can also be set with server.port or the SERVER_PORT environment variable as needed.

+

The application is setting the server port. To migrate a Java application that sets the server port to Azure Container Apps:

Azure Container Apps lets you configure port exposure according to your Azure Container Apps resource configuration. For instance, a Spring Boot application listens on port 8080 by default, but it can also be set with server.port or the SERVER_PORT environment variable as needed.

From 25574caf4a592d77dfb0da647a5f9a5299abc6c8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 May 2026 06:22:01 +0000 Subject: [PATCH 7/7] Apply final assessment HTML wording tweak Agent-Logs-Url: https://github.com/qianwens/PhotoAlbum-Java/sessions/82480c71-f7c9-403f-bbf2-d2fbf546a091 Co-authored-by: qianwens <37290631+qianwens@users.noreply.github.com> --- .../assessment/reports/report-20260521061104/report.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/modernize/assessment/reports/report-20260521061104/report.html b/.github/modernize/assessment/reports/report-20260521061104/report.html index e934f2e4e..fd8ab3a9e 100644 --- a/.github/modernize/assessment/reports/report-20260521061104/report.html +++ b/.github/modernize/assessment/reports/report-20260521061104/report.html @@ -388,7 +388,7 @@

Explanation

Explanation

-

The application is setting the server port. To migrate a Java application that sets the server port to Azure Container Apps:

Azure Container Apps lets you configure port exposure according to your Azure Container Apps resource configuration. For instance, a Spring Boot application listens on port 8080 by default, but it can also be set with server.port or the SERVER_PORT environment variable as needed.

+

The application is setting the server port. To migrate a Java application that sets the server port to Azure Container Apps:

Azure Container Apps allows you to configure which port to expose. For instance, a Spring Boot application listens on port 8080 by default, which can be customized using the server.port property or the SERVER_PORT environment variable.