Skip to content

usm/cli-multi-lang-scan [built]

Multi-language scanner support, extended with a detector plugin system. usm scan detects services, frameworks, routes, data models, and infrastructure from 12+ languages and 30+ frameworks via built-in detectors. Users and agents extend detection through two declarative surfaces — a detection section in usmconfig.json and auto-discovered .usm/detectors/*.yaml files — with a script escape hatch for convention-based frameworks. The scanner is generalized: service detection is driven by detector manifests, removing the package.json hardcode, so non-JS/TS stacks (Go, Zig, etc.) are first-class. Existing Node.js/TypeScript scanning is unchanged.

Status: built

Why this exists

The .usm format, MCP tools, generators, and validator are all language-agnostic, but the scanner only detects Node.js/TypeScript projects (package.json, Next.js routes, Prisma schemas) plus hardcoded Python/Docker/Prisma/Terraform special-cases. This feature extends the scanner to detect services and routes from common frameworks across all major programming languages AND makes the detection itself extensible without an upstream PR — users drop a .usm/detectors/zig.yaml file or add a detection entry to usmconfig.json, and usm scan picks it up. A script escape hatch handles convention-based frameworks (Next.js file-routing) that regex cannot express. The generalized orchestrator replaces the package.json hardcode so any detector manifest (go.mod, build.zig.zon, etc.) drives service detection.

Design decisions

manifest-based-detection [accepted]

Decision: Detect services by language manifest files

Rationale: Every language ecosystem has a standard manifest file. Detecting these files is reliable, language-specific, and doesn't require parsing source code. The manifest also tells us dependencies (framework detection).

Consequences: Scanner needs a manifest-to-language map. New languages added by adding a manifest entry.

framework-specific-route-detection [accepted]

Decision: Detect routes per-framework, not per-language

Rationale: Route patterns differ by framework, not language. Next.js uses app/page.tsx, FastAPI uses @app.get decorators, Spring uses @GetMapping annotations, Rails uses config/routes.rb, ASP.NET uses [HttpGet] attributes. Each framework needs its own route extractor.

Alternatives considered:

  • Generic AST parsing per language — rejected: Too complex, requires language-specific parsers, fragile
  • File-pattern matching only (no framework awareness) — rejected: Misses decorator/annotation-based routes (FastAPI, Spring, ASP.NET)

Consequences: Route detection is framework-by-framework. New frameworks need a route extractor.

configurable-in-usmconfig [accepted]

Decision: Language/framework detection rules are configurable in usmconfig.json

Rationale: Users can add custom manifest patterns, route patterns, and data model patterns for frameworks we don't support out of the box. Defaults cover common frameworks; advanced users can extend.

Consequences: usmconfig.json gains a 'detection' section with language/framework rules

all-languages-from-start [accepted]

Decision: Support all major languages from the initial implementation

Rationale: Rather than phasing, include all major languages (Python, Go, Rust, Java, Kotlin, C#, Ruby, PHP, Elixir, Swift, Scala, C/C++) from the start. The manifest detection is simple (file pattern matching) and route detection is regex-based per framework. The complexity is manageable.

Consequences: Larger initial implementation but no phased rollout complexity

detectors-directory [accepted]

Decision: Add .usm/detectors/*.yaml as a second auto-discovered extension surface alongside usmconfig.json detection

Rationale: usmconfig.json describes a single repo's shape and is per-project. Detector files are shareable across repos, versioned with the .usm source of truth, validatable like other USM artifacts, and agent-writable via MCP tools. Two surfaces with the same field shapes give users the cheap path (drop a file) and the config path (inline rules) without forcing one or the other.

Alternatives considered:

  • usmconfig.json detection only (original accepted decision) — rejected: Config is per-project and not shareable; cannot express convention-based frameworks that need code
  • .usm/detectors/ only, drop the config section — rejected: Reverses an accepted decision and removes the inline config ergonomics for small overrides

Consequences: Two surfaces to keep in sync; precedence rules must be deterministic. Built-in detectors migrate to the same shape as user detectors.

script-escape-hatch [accepted]

Decision: Allow routes.script in a detector to point at a .ts/.js file exporting extractRoutes(sourceDir, framework) for convention-based frameworks

Rationale: Regex cannot capture Next.js app/page.tsx convention routing, Remix, or SvelteKit file-based routes. These need real logic. A script field keeps the declarative detector file as the entry point but delegates extraction to code when needed. The script lives in the user's repo (not auto-loaded from third parties), bounding the trust surface.

Consequences: Detector files can reference local code; USM dynamically imports it. Only opt-in per detector; declarative regex remains the default.

generalized-orchestrator [accepted]

Decision: Generalize structural.ts service detection so any detector manifest drives it, removing the package.json hardcode

Rationale: Today a service directory without package.json is warned and skipped, making non-JS/TS stacks second-class. The whole point of multi-language scan is that a Go app (go.mod) or Zig app (build.zig.zon) is detected as a service without package.json. The orchestrator must ask detectors 'does your manifest match this directory' instead of assuming package.json.

Consequences: The Python pyproject.toml and Docker docker-compose special-case passes become built-in detectors. structural.ts shrinks to an orchestrator that iterates detectors.

infrastructure-as-detector [accepted]

Decision: Treat infrastructure (Terraform today) as a detector kind rather than a hardcoded separate subcommand

Rationale: infrastructure.ts only parses Terraform. Users with CloudFormation, Pulumi, or CDK get nothing. An infrastructure detector kind with the same manifest+pattern shape as other detectors makes IaC extensible without code, consistent with the rest of the plugin system.

Consequences: usm scan infrastructure subcommand continues to work (backed by the built-in Terraform detector); new formats add via detector files or detection.infrastructure in config.

precedence-order [accepted]

Decision: Detector precedence is built-in defaults < .usm/detectors/ files < usmconfig.json detection (last wins)

Rationale: Built-ins provide sane defaults out of the box. Detector files are project-level customizations shared across the team. usmconfig.json is the highest precedence so a user can override a detector file from config without editing the file (useful for one-off overrides). Deterministic order prevents merge ambiguity.

Consequences: Documented precedence; overrides by $id for detectors and by manifest pattern for config arrays.

How it works

Detect services from multiple language manifests (detect-multi-lang-services)

Scanner reads usmconfig.json detection.manifests (or defaults) and checks for manifest files. Each manifest identifies a service and its language. Framework is detected from dependencies in the manifest.

  1. Read — usmconfig.json detection.manifests (or defaults)
  2. Scan — directories for all manifest files
  3. Parse — each manifest for framework dependencies
  4. Classify — service type (web-app, api, worker) from framework
  5. Generate — .usm/services/<name>.usm with detected language, runtime, framework

Detect Python routes (FastAPI, Flask, Django) (detect-python-routes)

Scan .py files for decorator-based routes. FastAPI uses @app.get/@router.get, Flask uses @app.route, Django uses urlpatterns in urls.py.

  1. Scan — .py files in service directory
  2. Match — FastAPI: @app.(get|post|put|delete|patch), @router.(get|post|...); Flask: @app.route; Django: path('', views)
  3. Extract — path and HTTP method from decorator/pattern
  4. Generate — routes[] in feature .usm files

Detect Go routes (chi, gin, echo, net/http) (detect-go-routes)

Scan .go files for router registration patterns. chi/gin/echo use r.GET/r.POST, net/http uses http.HandleFunc, gorilla/mux uses r.HandleFunc.

  1. Scan — .go files in service directory
  2. Match — (r|router|mux).(GET|POST|PUT|DELETE|PATCH|HandleFunc|Handle)
  3. Extract — path and HTTP method
  4. Generate — routes[] in feature .usm files

Detect Rust routes (Axum, Actix, Rocket) (detect-rust-routes)

Scan .rs files for route patterns. Axum uses .route(), Actix uses #[get(...)] macros, Rocket uses #[get("/path")] attributes.

  1. Scan — .rs files in service directory
  2. Match — .route(), .service(), #[get(...)], #[post(...)], #[route(GET, "/path")]
  3. Extract — path and HTTP method
  4. Generate — routes[] in feature .usm files

Detect Java/Kotlin routes (Spring Boot, Javalin, Quarkus) (detect-java-routes)

Scan .java/.kt files for annotation-based routes. Spring uses @GetMapping/@PostMapping/@RequestMapping, Javalin uses app.get/post, Quarkus uses @Path + @GET/@POST.

  1. Scan — .java and .kt files in service directory
  2. Match — Spring: @GetMapping/@PostMapping/@RequestMapping; Javalin: app.get/post; Quarkus: @GET/@POST + @Path
  3. Extract — path and HTTP method from annotation
  4. Generate — routes[] in feature .usm files

Detect C# routes (ASP.NET Core, Minimal APIs) (detect-csharp-routes)

Scan .cs files for attribute-based routes. ASP.NET uses [HttpGet], [HttpPost], [Route], Minimal APIs use app.MapGet/MapPost.

  1. Scan — .cs files in service directory
  2. Match — [HttpGet], [HttpPost], [Route("path")], app.MapGet, app.MapPost
  3. Extract — path and HTTP method from attribute
  4. Generate — routes[] in feature .usm files

Detect Ruby routes (Rails, Sinatra) (detect-ruby-routes)

Scan config/routes.rb for Rails routes (get/post/resources) and .rb files for Sinatra routes (get '/path' do).

  1. Scan — config/routes.rb and .rb files in service directory
  2. Match — Rails: get/post/resources/namespace; Sinatra: get '/path' do, post '/path' do
  3. Extract — path and HTTP method
  4. Generate — routes[] in feature .usm files

Detect PHP routes (Laravel, Symfony, Slim) (detect-php-routes)

Scan routes/web.php for Laravel routes (Route::get/post), .php files for Symfony attributes (#[Route]) and Slim ($app->get/post).

  1. Scan — routes/web.php, routes/api.php, and .php files
  2. Match — Laravel: Route::get/post; Symfony: #[Route]; Slim: $app->get/post
  3. Extract — path and HTTP method
  4. Generate — routes[] in feature .usm files

Detect Elixir routes (Phoenix) (detect-elixir-routes)

Scan router.ex for Phoenix routes (get/post/pipe_through/scope).

  1. Scan — lib/*_web/router.ex files
  2. Match — get/post/put/patch/delete within scope blocks
  3. Extract — path and HTTP method
  4. Generate — routes[] in feature .usm files

Detect Swift routes (Vapor) (detect-swift-routes)

Scan .swift files for Vapor route registrations (routes.get/post, app.get/post).

  1. Scan — .swift files in service directory
  2. Match — routes.get/post/put/delete, app.get/post
  3. Extract — path and HTTP method
  4. Generate — routes[] in feature .usm files

Detect Scala routes (Akka HTTP, Play, Tapir) (detect-scala-routes)

Scan .scala files for route patterns. Akka HTTP uses path/endpoints, Play uses routes file, Tapir uses endpoint.get/post.

  1. Scan — .scala files and conf/routes files
  2. Match — Akka: path/endpoints; Play: GET /path; Tapir: endpoint.get/post
  3. Extract — path and HTTP method
  4. Generate — routes[] in feature .usm files

Detect C++ routes (Crow, Drogon, Pistache) (detect-cpp-routes)

Scan .cpp/.h files for route registration patterns. Crow uses CROW_ROUTE, Drogon uses app.registerHandler, Pistache uses router.get/post.

  1. Scan — .cpp and .h files in service directory
  2. Match — Crow: CROW_ROUTE; Drogon: registerHandler; Pistache: router.get/post
  3. Extract — path and HTTP method
  4. Generate — routes[] in feature .usm files

Detect data models from multiple ORMs across languages (detect-multi-lang-data-models)

Extend data model detection beyond Prisma to support ORMs across all supported languages.

  1. Scan — known ORM schema files per language
  2. Parse — model definitions (class-based, struct-based, macro-based, annotation-based)
  3. Generate — .usm/data/<name>.usm with models and fields

Discover and load detector files (discover-detector-files)

At scan start, the orchestrator reads .usm/detectors/*.yaml (if present), validates each against detector-v1.json, and merges them with built-in defaults and the usmconfig.json detection section. Precedence: built-in defaults < .usm/detectors/ files < usmconfig.json detection (last wins).

  1. Read — .usm/detectors/*.yaml if directory exists
  2. Validate — each detector file against detector-v1.json schema
  3. Merge — built-in defaults + detector files + usmconfig.json detection (config overrides files)
  4. Warn — on invalid detector files (skip, do not abort scan)

Detect a service from any language manifest (detect-service-via-detector)

Replaces the hardcoded package.json read. For each directory matched by a services rule, the orchestrator finds the detector whose manifest glob matches a file in that directory and reads that manifest (not always package.json). A Go app with go.mod is detected via the Go detector; a Zig app with build.zig.zon via the Zig detector. No package.json is required.

  1. Match — directory against usmconfig.json services rules
  2. Find — detector whose manifest glob matches a file in the directory
  3. Parse — that manifest file (package.json, go.mod, build.zig.zon, etc.)
  4. Classify — language, runtime, framework from manifest contents
  5. Generate — .usm/apps/<name>/service.usm

Extract routes for convention-based frameworks via script (extract-routes-via-script)

Frameworks whose routes come from file conventions (Next.js app/page.tsx, Remix, SvelteKit) rather than declarations cannot be captured by regex. A detector declares routes.script pointing at a .ts file exporting extractRoutes(sourceDir, framework). The orchestrator imports and runs it. Declarative regex patterns remain the default; script is opt-in per detector.

  1. Read — detector routes.script path
  2. Import — the script module dynamically
  3. Call — extractRoutes(sourceDir, framework)
  4. Generate — routes[] in feature .usm files

Detect infrastructure from IaC files via detectors (detect-infrastructure-via-detector)

Generalizes the Terraform-only infrastructure scan. An infrastructure detector declares a manifest glob (infrastructure/**/.tf, **/cloudformation.yaml, **/.pulumi.ts) and resource extraction patterns. Built-in Terraform detector preserves current behaviour; users add CloudFormation/Pulumi/CDK via detector files or usmconfig.json detection.infrastructure.

  1. Match — infrastructure detector manifest globs
  2. Parse — matched IaC files for resources
  3. Emit — infrastructure YAML block per service

Detect data models from any ORM via detectors (detect-data-schema-via-detector)

Generalizes the Prisma-only data scan. A data-schema detector declares a manifest glob and model extraction patterns. Built-in Prisma detector preserves current behaviour; users add SQLAlchemy, Diesel, GORM, Entity Framework via detector files or usmconfig.json detection.data_models.

  1. Match — data-schema detector manifest globs
  2. Parse — model definitions (class, struct, macro, annotation)
  3. Generate — .usm/packages/<pkg>/<orm>.usm with models[]

Guarantees

manifest-detection-coverage

Scanner detects services from all supported language manifests

Acceptance criteria:

  • [ ] package.json → Node.js/TypeScript/JavaScript
  • [ ] pyproject.toml or requirements.txt → Python
  • [ ] Cargo.toml → Rust
  • [ ] go.mod → Go
  • [ ] pom.xml or build.gradle → Java/Kotlin
  • [ ] .csproj or .sln → C#/.NET
  • [ ] Gemfile → Ruby
  • [ ] composer.json → PHP
  • [ ] mix.exs → Elixir
  • [ ] Package.swift → Swift
  • [ ] build.sbt → Scala
  • [ ] CMakeLists.txt or Makefile → C/C++
  • [ ] Framework detected from dependencies in manifest

route-detection-coverage

Scanner detects routes from 30+ frameworks across all languages

Acceptance criteria:

  • [ ] Next.js: app/page.tsx, app/route.ts (existing)
  • [ ] Express: app.get/post/put/delete in .js/.ts files
  • [ ] FastAPI: @app.get/post decorators in .py files
  • [ ] Flask: @app.route decorators in .py files
  • [ ] Django: path() patterns in urls.py
  • [ ] Go chi/gin/echo: r.GET/POST and router.HandleFunc patterns
  • [ ] Go net/http: http.HandleFunc patterns
  • [ ] Rust Axum: .route() calls
  • [ ] Rust Actix: #[get/post] macros
  • [ ] Rust Rocket: #[get/post] attributes
  • [ ] Spring Boot: @GetMapping/@PostMapping/@RequestMapping annotations
  • [ ] Javalin: app.get/post calls
  • [ ] Quarkus: @GET/@POST + @Path annotations
  • [ ] ASP.NET Core: [HttpGet]/[HttpPost] attributes
  • [ ] ASP.NET Minimal: app.MapGet/MapPost
  • [ ] Rails: get/post/resources in config/routes.rb
  • [ ] Sinatra: get '/path' do in .rb files
  • [ ] Laravel: Route::get/post in routes/web.php
  • [ ] Symfony: #[Route] attributes
  • [ ] Slim: $app->get/post in .php files
  • [ ] Phoenix: get/post in router.ex
  • [ ] Vapor: routes.get/post in .swift files
  • [ ] Akka HTTP: path/endpoints in .scala files
  • [ ] Play: GET /path in conf/routes
  • [ ] Tapir: endpoint.get/post in .scala files
  • [ ] Crow: CROW_ROUTE macro in .cpp files
  • [ ] Drogon: registerHandler in .cpp files
  • [ ] Pistache: router.get/post in .cpp files

data-model-detection-coverage

Scanner detects data models from ORMs across languages

Acceptance criteria:

  • [ ] Prisma: schema.prisma (existing, TypeScript)
  • [ ] SQLAlchemy: class definitions in models.py (Python)
  • [ ] Django ORM: class definitions in models.py (Python)
  • [ ] GORM: struct definitions with gorm tags (Go)
  • [ ] Diesel: table! macros in schema.rs (Rust)
  • [ ] Hibernate: @Entity annotations in .java (Java)
  • [ ] Entity Framework: DbSet properties in DbContext (C#)
  • [ ] ActiveRecord: class definitions inheriting ApplicationRecord (Ruby)
  • [ ] Eloquent: class definitions extending Model (PHP)
  • [ ] Ecto: schema definitions in .ex files (Elixir)

config-extensible

Detection rules are configurable in usmconfig.json

Acceptance criteria:

  • [ ] detection.manifests array with {pattern, language, frameworks?} objects
  • [ ] detection.routes array with {framework, pattern, method_group, path_group} objects
  • [ ] detection.data_models array with {orm, pattern, model_pattern?} objects
  • [ ] Defaults cover all supported frameworks; users can add custom patterns

backward-compatible

Existing Node.js/TypeScript scanning unchanged

Acceptance criteria:

  • [ ] usm scan on a Node.js project produces same results as before
  • [ ] usmconfig.json without detection section uses defaults

detector-file-format

Detector files in .usm/detectors/*.yaml are validated against a detector-v1.json schema before use

Acceptance criteria:

  • [ ] Each detector declares $id, kind (service|framework|routes|data-schema|infrastructure), manifest glob, language, runtime
  • [ ] Framework detectors declare frameworks[] with name + string-contains detect rule
  • [ ] Route detectors declare routes.extensions + routes.patterns[] (regex, methodGroup, pathGroup) OR routes.script (mutually exclusive)
  • [ ] Invalid detector files are skipped with a warning, not an abort

dual-extension-surfaces

Users can extend detection through either usmconfig.json or .usm/detectors/ files, with the same expressiveness

Acceptance criteria:

  • [ ] usmconfig.json detection.manifests / detection.routes / detection.data_models / detection.infrastructure arrays (existing accepted decision, unchanged)
  • [ ] .usm/detectors/*.yaml files as a second, auto-discovered source
  • [ ] Both surfaces accept the same field shapes (manifest, frameworks, routes patterns, data model patterns)
  • [ ] Detector files support routes.script for convention-based frameworks; usmconfig.json detection.routes supports script too

orchestrator-generalized

Service detection is driven by detector manifests, removing the package.json hardcode in structural.ts

Acceptance criteria:

  • [ ] A service directory is detected if ANY detector manifest glob matches a file in it (not only package.json)
  • [ ] A Go app with go.mod and no package.json produces a service.usm (no 'No package.json found' warning)
  • [ ] A Zig app with build.zig.zon and no package.json produces a service.usm
  • [ ] The existing Python pyproject.toml and Docker docker-compose passes become built-in detectors, not special-case code

infrastructure-extensible

Infrastructure detection is extensible beyond Terraform via detectors

Acceptance criteria:

  • [ ] Built-in Terraform detector preserves usm scan infrastructure output exactly
  • [ ] detection.infrastructure array in usmconfig.json (new, mirrors detection.manifests shape)
  • [ ] Infrastructure detector files accepted in .usm/detectors/
  • [ ] CloudFormation, Pulumi, CDK detectable via user-supplied detectors without code change

precedence-and-override

Detector sources merge with a deterministic precedence so users can override built-ins

Acceptance criteria:

  • [ ] Built-in detectors are the lowest precedence (seeded from current MANIFESTS/ROUTE_PATTERNS/Prisma/Terraform tables)
  • [ ] .usm/detectors/ files override built-ins by detector $id
  • [ ] usmconfig.json detection section is highest precedence (overrides both built-ins and detector files)
  • [ ] A usmconfig.json with no detection section and no .usm/detectors/ dir produces identical results to the current scanner (backward compatible)

backward-compatible-existing-implementations

Existing USM projects with usmconfig.json and .usm files continue to scan identically

Acceptance criteria:

  • [ ] usm scan on a Node.js/Next.js project with no detection section and no detectors dir produces the same .usm output as before this feature
  • [ ] Existing PRESERVE_FIELDS / UPDATE_FIELDS smart-merge behaviour unchanged
  • [ ] Existing CLI flags (--force, --routes, --merge) unchanged in name and default
  • [ ] The existing manifest-detection-coverage and route-detection-coverage contracts from the original spec remain satisfied by the migrated built-in detectors

Test specifications

detect-python-service

Given:

  • pyproject_toml_with_fastapi: true

Then:

  • assertion: .usm/services/<name>.usm created with runtime: python
  • assertion: framework: fastapi detected

detect-go-service

Given:

  • go_mod_with_gin: true

Then:

  • assertion: .usm/services/<name>.usm created with runtime: go
  • assertion: framework: gin detected

detect-rust-service

Given:

  • cargo_toml_with_axum: true

Then:

  • assertion: .usm/services/<name>.usm created with runtime: rust
  • assertion: framework: axum detected

detect-java-service

Given:

  • pom_xml_with_spring_boot: true

Then:

  • assertion: .usm/services/<name>.usm created with runtime: java
  • assertion: framework: spring-boot detected

detect-csharp-service

Given:

  • csproj_with_aspnet: true

Then:

  • assertion: .usm/services/<name>.usm created with runtime: dotnet
  • assertion: framework: aspnet-core detected

detect-ruby-service

Given:

  • gemfile_with_rails: true

Then:

  • assertion: .usm/services/<name>.usm created with runtime: ruby
  • assertion: framework: rails detected

detect-php-service

Given:

  • composer_json_with_laravel: true

Then:

  • assertion: .usm/services/<name>.usm created with runtime: php
  • assertion: framework: laravel detected

detect-elixir-service

Given:

  • mix_exs_with_phoenix: true

Then:

  • assertion: .usm/services/<name>.usm created with runtime: elixir
  • assertion: framework: phoenix detected

detect-swift-service

Given:

  • package_swift_with_vapor: true

Then:

  • assertion: .usm/services/<name>.usm created with runtime: swift
  • assertion: framework: vapor detected

detect-scala-service

Given:

  • build_sbt_with_akka: true

Then:

  • assertion: .usm/services/<name>.usm created with runtime: scala
  • assertion: framework: akka-http detected

detect-cpp-service

Given:

  • cmake_with_drogon: true

Then:

  • assertion: .usm/services/<name>.usm created with runtime: cpp
  • assertion: framework: drogon detected

extract-fastapi-routes

Given:

  • python_file_with_decorators: true

Then:

  • assertion: routes[] extracted with correct paths and methods

extract-spring-routes

Given:

  • java_file_with_annotations: true

Then:

  • assertion: routes[] extracted from @GetMapping/@PostMapping

extract-aspnet-routes

Given:

  • cs_file_with_attributes: true

Then:

  • assertion: routes[] extracted from [HttpGet]/[HttpPost]

extract-rails-routes

Given:

  • routes_rb_with_get_post: true

Then:

  • assertion: routes[] extracted from get/post/resources

extract-laravel-routes

Given:

  • web_php_with_route_get: true

Then:

  • assertion: routes[] extracted from Route::get/post

extract-phoenix-routes

Given:

  • router_ex_with_get_post: true

Then:

  • assertion: routes[] extracted from get/post in scope blocks

backward-compatible-node

Given:

  • package_json_with_nextjs: true

Then:

  • assertion: same results as before this feature

custom-detection-rules

Given:

  • usmconfig_with_custom_manifest: true

Then:

  • assertion: custom manifest pattern detected

detector-file-zig-detected

Given:

  • usm_detectors_zig_yaml: "declares manifest build.zig.zon, framework zap, route regex"
  • apps_zig_api_build_zig_zon_with_zap: true

Then:

  • assertion: .usm/apps/zig-api/service.usm created with runtime: zig, framework: zap
  • assertion: no 'No package.json found' warning
  • assertion: routes[] extracted from .zig source files

infrastructure-detector-cloudformation

Given:

  • usm_detectors_cloudformation_yaml: "declares manifest **/cloudformation.yaml, resource extraction pattern"
  • infrastructure_cloudformation_yaml_with_resources: true

Then:

  • assertion: infrastructure YAML block emitted for CloudFormation resources

script-route-extraction-nextjs

Given:

  • usm_detectors_nextjs_yaml: "declares routes.script: ./detectors/nextjs.ts"
  • apps_web_app_page_tsx: true

Then:

  • assertion: routes[] extracted by the script, matching the current Next.js route extraction output

config-overrides-detector-file

Given:

  • usm_detectors_go_yaml: "declares framework gin detect 'gin'"
  • usmconfig_detection_manifests: "overrides go framework detect to 'echo'"

Then:

  • assertion: scan uses the usmconfig.json detection rule (echo), not the detector file (gin)

no-package-json-service-detected

Given:

  • apps_go_api_go_mod_with_gin: true
  • no_package_json: true

Then:

  • assertion: .usm/apps/go-api/service.usm created with runtime: go, framework: gin
  • assertion: no warning emitted

backward-compatible-no-detectors-dir

Given:

  • package_json_with_nextjs: true
  • no_usm_detectors_dir: true
  • usmconfig_without_detection_section: true

Then:

  • assertion: scan output identical to pre-feature scanner (same services, routes, features)

invalid-detector-file-skipped

Given:

  • usm_detectors_bad_yaml: "malformed YAML or missing required fields"

Then:

  • assertion: scan continues, warning emitted naming the invalid file
  • assertion: other detectors still load and run