From nolte-shared
Generates a schema-valid Backstage catalog-info.yaml from an existing software project, conforming to spec/project/backstage-catalog-generation/. Inspects the repo (language/structure, remote slug, CODEOWNERS, OpenAPI/AsyncAPI/GraphQL/gRPC specs, colocated docs), infers the per-kind MUST-floor fields, confirms what it cannot infer (owner, spec.system, dependsOn, lifecycle) with the operator, writes the descriptor at the repo root, and self-validates. Invoke when the user asks to "generate a catalog-info.yaml", "onboard this repo into Backstage", "create a Backstage Software Catalog entity", "add this service to our developer portal", or German equivalents ("erzeuge eine catalog-info.yaml", "nimm dieses Repo in Backstage auf"). Optionally emits a Tech Radar TechRadarLoaderResponse JSON (never a catalog entity). Don't use to configure a Backstage backend / catalog.providers, install the Tech Radar UI plugin, or ingest Group/User org entities. Single-shot; resume not applicable.
How this skill is triggered — by the user, by Claude, or both
Slash command
/nolte-shared:backstage-catalog-generateThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Turns an existing software project into a schema-valid Backstage `catalog-info.yaml` at the repository root — inferring what the repo reveals, asking the operator for what it does not, and self-validating before it presents the result. The authoritative rules live in **`spec/project/backstage-catalog-generation/`**; this skill operationalises that spec. When the spec and this body disagree, the...
Turns an existing software project into a schema-valid Backstage catalog-info.yaml at the repository root — inferring what the repo reveals, asking the operator for what it does not, and self-validating before it presents the result. The authoritative rules live in spec/project/backstage-catalog-generation/; this skill operationalises that spec. When the spec and this body disagree, the spec wins.
/nolte-shared:backstage-catalog-generate against the current repo; the operator drives it directly.spec.system, dependsOn, and an uncertain lifecycle cannot be inferred safely from one repo; the skill surfaces each as a confirmation gate rather than guessing. An agent's fire-and-forget shape would lose those gates.The skill reads these signals and maps them to descriptor fields (every inferred value is recorded as inferred, distinct from confirm):
| Signal | Field | Notes |
|---|---|---|
| repo / project name | metadata.name | slugify, strip leading/trailing non-alphanumerics, cap 63 → must satisfy isValidObjectName |
| primary language / structure | spec.type | service / website / library (Component); convention, not a fixed enum |
git remote slug | github.com/project-slug, backstage.io/source-location | org/repo; source-location is url:…/<repo>/ with trailing slash |
CODEOWNERS / team slug | spec.owner | bare slug → Group; an individual needs an explicit user: prefix |
openapi.yaml / asyncapi.yaml / *.graphql / *.proto | an API entity + spec.providesApis | API spec.definition via $text: ./<relative-path>, spec.type = openapi/asyncapi/graphql/grpc |
colocated docs/ / mkdocs.yml | backstage.io/techdocs-ref: dir:. | only when docs are colocated |
README description / package.json etc. | metadata.description, metadata.tags | tags match ^[a-z0-9:+#]+(\-[a-z0-9:+#]+)*$ |
spec.lifecycle, spec.system, spec.dependsOn, and spec.domain are never inferred silently — default lifecycle only on a justifying signal, otherwise confirm; system/dependsOn/domain always require operator input.
Component, plus an API entity when an interface definition is found. Consult the per-kind tables in spec/project/backstage-catalog-generation/ §"Entity kinds" for the required/optional fields of any other kind.apiVersion: backstage.io/v1alpha1, kind: Component, a valid metadata.name, spec.type, spec.lifecycle, spec.owner. Mark each value inferred or needs-confirm.lifecycle, optional system/dependsOn) to the operator in one pass. Never emit a guessed system/dependsOn/domain; never emit an owner that cannot be confirmed to resolve — flag it as operator-action-required instead.catalog-info.yaml at the repository root (matching the discovery default path). Use multi-entity --- separators when emitting a Component plus its API. Quote any numeric/boolean-looking annotation value so it stays a string.@roadiehq/backstage-entity-validator when available (npx @roadiehq/backstage-entity-validator catalog-info.yaml); otherwise apply the §Hard rules checklist below as a deterministic pre-flight. Treat reference-target existence (owner/system/API) as unverified by the offline check — surface those references as claims to confirm, not validated facts.When the operator asks for a Tech Radar output: emit a TechRadarLoaderResponse JSON (quadrants, rings, entries) per spec/project/backstage-catalog-generation/ §"The Tech Radar data model" — not a catalog-info.yaml, and never modelled as a catalog entity. Express each entry's ring placement through its timeline (a snapshot with ringId and a coercible date), not as a direct field. Target the @backstage-community package model; never reference the deprecated @backstage Tech Radar package or the dead backstage.io/docs/features/techradar/ URLs.
Backstage's real validation is stricter and less obvious than its prose docs — these are the quirks that bite a generator:
@roadiehq/backstage-entity-validator validates schema and field-format only; an owner/system/API reference can pass and still dangle at processing time. Always surface references as claims to confirm against the target catalog, never as validated facts.typeof string; an unquoted github.com/user-id: 123456 or backstage.io/orphan: true parses as a number/bool and fails. Quote them.metadata.name is stricter than "alphanumerics separated by [-_.]". The first and last character must be alphanumeric — a slug that ends in - (common after naive slugification) is rejected by FieldFormatEntityPolicy at processing time, after it passed coarse ingestion.Resource has no lifecycle even though Component and API do — emitting it is invalid. Symmetrically, Group.children and User.memberOf must be present even when empty ([]).backstage-cli validate command. Validation is either the @roadiehq validator or a POST to a running backend's /api/catalog/validate-entity (where location goes in the JSON body, not an HTTP header).@backstage-community. The old @backstage Tech Radar package and the backstage.io/docs/features/techradar/ URLs are dead — never cite them.backstage.io/managed-by-location, backstage.io/managed-by-origin-location, backstage.io/orphan, metadata.uid, metadata.etag, relations, status.spec.lifecycle on Resource, System, Domain, Group, or User — only Component and API carry it.spec.children: [] on a Group with no known children; spec.memberOf: [] on a User with no known memberships.spec.definition, supplied via a $text: placeholder, and the providing Component lists the API under providesApis.metadata.name must satisfy isValidObjectName: length 1–63, first and last character alphanumeric, separators [-_.] interior only. metadata.namespace is a DNS label (lowercase, no underscore/dot).user:-prefixed reference for an individual. Prefer the full kind:namespace/name form for cross-system robustness.backstage.io/github-actions-id → github.com/project-slug; backstage.io/definition-at-location → placeholder substitution; jenkins.io/github-folder → jenkins.io/job-full-name.catalog-info.yaml is a backend, frontend-agnostic concern.spec/project/backstage-catalog-generation/ disagrees with this body, the spec wins; propose updating this skill rather than diverging.Guides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.
npx claudepluginhub nolte/claude-shared --plugin nolte-media