Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@ The two failure modes most likely to bite an agent here are:

Before declaring a task done, check `CONTRIBUTING.md` for the conventions in _Adding a new codemod_ (directory layout, package scope, single-quoted YAML) and run the checks listed in _Making changes_.

## Agent skills

### Issue tracker

GitHub Issues on `backstage/codemods` (via the `gh` CLI). See `docs/agents/issue-tracker.md`.

### Triage labels

Default five-label vocabulary: `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`. See `docs/agents/triage-labels.md`.

### Domain docs

Single-context: `GLOSSARY.md` + `docs/adr/` at the repo root (created lazily). See `docs/agents/domain.md`.

## Planning a release migration

Before implementing codemods for a new Backstage release, use the project skill [`.agents/skills/backstage-codemod-issue-research/SKILL.md`](./.agents/skills/backstage-codemod-issue-research/SKILL.md) to research breaking changes, classify codemod vs document-only items, produce an inventory table, and draft GitHub issue specs. Implementation starts only after issues are filed and approved — then use the project [`codemod` skill](./.agents/skills/codemod/SKILL.md). Pi and other Agent Skills-compatible agents can load it from `.agents/skills/`; use `npx codemod ai` for its CLI commands.
32 changes: 32 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Context

Codemod recipes for Backstage release migrations, published to the [Codemod registry](https://codemod.com). One recipe package per Backstage release under `codemods/<version>/`, plus a misc recipe under `codemods/misc/`. Repository tooling lives in the private `packages/scripts` workspace package.

## Layout

```
codemods/<version>/ # one published migration-recipe package per release
scripts/<name>.ts # transform entrypoints (js-ast-grep targets)
tests/<name>/ # JSSG fixture suites + node:test suites
workflow.yaml # steps reference local scripts
codemod.yaml / package.json / README.md
codemods/misc/ # non-release recipes (mui4-to-bui)
packages/scripts/ # gates, generators, release helpers, recipe runner
docs/agents/ # agent config: tracker, triage labels, domain rules
docs/adr/ # architecture decision records
.agents/skills/ # repo skills (codemod authoring, release research)
```

## Where knowledge lives

- **Contributor conventions and release flow**: [`CONTRIBUTING.md`](./CONTRIBUTING.md) — read end-to-end before opening a PR.
- **Agent pointers and failure modes**: [`AGENTS.md`](./AGENTS.md), [`REVIEW.md`](./REVIEW.md).
- **Architectural decisions and why**: [`docs/adr/`](./docs/adr/).
- **Repo skills**: [`.agents/skills/`](./.agents/skills/) — `codemod` (implementation), `backstage-codemod-issue-research` (release planning), `validate-codemod-recipe`.
- **Agent config**: [`docs/agents/`](./docs/agents/) — issue tracker, triage labels, domain docs.

## Working rules that are easy to miss

- Every PR that changes a codemod needs a changeset — CI does not enforce it.
- Versions in `package.json` / `codemod.yaml` are owned by Changesets and the release pipeline; never hand-edit.
- The root README is generated; it regenerates on the Version Packages PR, so codemod PRs leave it unchanged.
30 changes: 30 additions & 0 deletions REVIEW.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Review

What failure looks like in this repo, for AI reviewers. A PR that passes CI can still be wrong in every way listed here — CI deliberately does not block on most of it.

## Release-blocking mistakes

- **No changeset.** A PR that changes a codemod must include `yarn changeset` output in `.changeset/`. CI will not remind anyone; without it the codemod is never tagged or published. (Docs/tooling-only PRs need none.)
- **Hand-edited versions.** `version` in `package.json` or `codemod.yaml` edited manually "to trigger a release". The Changesets bot owns both; manual edits are overwritten and cause version drift.
- **Hand-edited root README.** It is generated by `generate-readme-codemods` on the Version Packages PR. Codemod PRs leave it alone; drift is fixed on the next release PR or via `yarn readme`.

## Recipe-structure mistakes

- **Registry steps in `workflow.yaml`.** Every `js-ast-grep` step's `js_file` and every run step's `CODEMOD_SCRIPT` must be a local script; `codemod:` registry references are rejected by the test runner.
- **Wrong languages in `codemod.yaml`.** `targets.languages` must be the union of languages actually scanned by workflow steps (file selectors + run-step target files). Advertising a language nothing scans, or broadening selectors to match the manifest, is a bug.
- **Broken workflow-state coupling.** Steps that share state (an indexing step feeding a transform, a shell step reading state set by another) must run in the same workflow, in order. Reordering them "for tidiness" breaks the recipe.
- **Unscoped or wrong package names.** `package.json` `name` must use the `@backstage/` scope; the tagging/publishing scripts assume it.
- **Style drift in YAML.** Single quotes for string scalars in `codemod.yaml` and `workflow.yaml` (oxfmt convention); ids, type values, and booleans stay bare.

## Cross-platform mistakes

- **CRLF in fixtures.** Test fixtures and inputs must use LF; CRLF breaks cross-platform test comparison (a repeated failure mode in this repo's history).
- **Unbatched file lists.** Changed-file checks must batch paths (Windows argv limit), and must skip ignored test fixtures.

## Test mistakes

- **Ad-hoc test wiring instead of convention.** New steps don't change the test command: `scripts/<name>.ts` + `tests/<name>/<case>/input.<ext>` fixtures are discovered automatically; `tests/<name>/assert-fixtures.ts` covers whole-app trees; `tests/**/*.test.ts` runs under `node --test`; every `tests/**/tsconfig.json` is type-checked. Adding a bespoke wrapper suite instead of following the convention is a red flag.

## What good looks like

`yarn format`, `yarn lint`, `yarn test` pass locally — and `yarn check:changed` runs the same changed-file gate CI runs (format, lint, package-name, affected recipe tests), so that gate cannot drift. CI additionally runs `yarn test:scripts` (the `packages/scripts` regression tests) and a Changeset Check job — run `yarn test:scripts` locally before pushing tooling changes. The pre-commit (`yarn lint-staged`) and pre-push (`yarn check:changed`) hooks fail loudly rather than silently skipping.
20 changes: 20 additions & 0 deletions docs/adr/0001-yarn4-oxfmt-toolchain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# ADR 0001: Yarn 4 toolchain with oxfmt/oxlint and changed-file gates

- **Status**: Accepted
- **Date**: 2026-04-16 → 2026-10-13
- **Evidence**: `1ee5c96` (switch to Yarn), `9b9ec7d` (Corepack + Yarn 4 cache order), `933d821` (lockfile v9 under Yarn 4.14.1), `aab8333` (untrack install-state.gz), `b5e7d52` (oxfmt/oxlint/typescript configs), `ee079f8` (husky + lint-staged), `2502ea2` (CI format/lint checks), `6e1d555` (CI scoped to changed files), `ac50c6e` (batch changed-file checks for Windows argv limit), `42f0c8b`+`8e9a0c7` (check-changed from @backstage/scripts)

## Context

The repo started as npm-style standalone codemod packages with no shared formatting or CI gates. Per-codemod configs drifted, CI ran everything on every PR, and Windows contributors hit argv-length limits and CRLF mismatches.

## Decision

- Yarn 4 via Corepack; `yarn.lock` committed (lockfile v9), `.yarn/install-state.gz` untracked.
- oxfmt + oxlint (not Prettier/ESLint) as root tooling; recipes keep recipe-local `tsconfig.json` files, and the newer recipes (`v1.54.0+`) declare their own `typescript` devDependency.
- husky pre-commit runs `yarn lint-staged`; pre-push runs `yarn check:changed`; CI runs the same `check-changed` bin from the private `@backstage/scripts` package, so local and remote gates cannot drift.
- Changed-file checks batch paths and skip ignored test fixtures.

## Consequences

Fast PRs (only changed packages gate), one formatting dialect, loud failures when `node_modules` is missing in worktrees. Contributors must use Corepack-enabled Yarn, and Windows argv/CRLF pitfalls are handled centrally rather than per-PR.
17 changes: 17 additions & 0 deletions docs/adr/0002-flattened-recipe-layout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# ADR 0002: One flattened migration recipe per Backstage release

- **Status**: Accepted
- **Date**: 2026-10-27 → 2026-11-03
- **Evidence**: `de65869` (bundle 1.54 scripts into the recipe), `fdf77d1` (flatten v1.54.0 + adapt tooling), `f585c23`, `138ae39`, `2d983e2`, `7652a9c`, `e53eaab`, `6d0d7e4`, `57363b7`, `930b994` (fold v1.45–v1.55 into flattened recipes), `defbd23` (fold misc MUI codemods into mui4-to-bui recipe), `d21a333` (integrate flattened layout), `8f470fe` (workflow structural validation)

## Context

Codemods were originally standalone registry packages, with recipe workflows referencing them as `codemod:` registry steps. That meant many published packages per release, version coupling between recipe and steps, and no guarantee that a workflow step existed in the checkout it ran from.

## Decision

Each Backstage release publishes exactly one `migration-recipe` package at `codemods/<version>/`. Workflow steps run local scripts (`js_file: scripts/<name>.ts`); `codemod:` registry steps are structurally rejected by the test runner. The misc recipe keeps its nested root at `codemods/misc/mui4-to-bui-migration-recipe/`. Tooling discovers recipe package roots by convention.

## Consequences

Single published artifact per release, workflow and scripts always version together, and recipe PRs are self-contained. Adding a cross-release shared codemod now requires either duplication or folding into an existing recipe — accepted cost.
17 changes: 17 additions & 0 deletions docs/adr/0003-ast-first-ai-fixup-fallback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# ADR 0003: AST-first codemods, AI fixup only as fallback

- **Status**: Accepted
- **Date**: 2026-04-23
- **Evidence**: `32517ac` (deterministic duck-typing from AI to AST), `a882bc6` (namespace/dynamic import handling from AI to AST), `4526cae` (template literal scanning from AI to AST), `aae72ff` + `ed1608d` (deterministic patterns from AI fixup into AST codemod), `d3734c0` (semantic analysis suggestions), `b5f7380`, `2d936f2` (further AI→AST moves)

## Context

Recipe workflows originally used AI fixup steps for fuzzy transformations (renames scoped to a receiver type, import handling, duck-typed object matching). AI steps are nondeterministic and hard to test. Moving the patterns into deterministic AST transforms then required a series of follow-up fixes to tighten receiver and type scoping (`d12b8fc` guard on call arguments, `8b709dc` narrowing to return types, `38f173a` scoping renames to `DialogApi` receivers) — the cost of getting deterministic detection right.

## Decision

Any pattern that can be expressed deterministically moves into the AST codemod (`js-ast-grep` script with semantic analysis: type anchors, `ref.inside()` for conditional/loop detection, receiver-scoped renames). AI fixup steps remain only for genuinely fuzzy edits the AST cannot decide.

## Consequences

Deterministic, fixture-testable transforms; false-positive fixes land once in the script instead of per-run. Residual AI steps are the explicitly acknowledged fuzzy surface — new PRs should justify keeping a pattern in AI rather than moving it to AST.
17 changes: 17 additions & 0 deletions docs/adr/0004-convention-based-test-runner.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# ADR 0004: Convention-based test runner in a private scripts package

- **Status**: Accepted
- **Date**: 2026-10-27 → 2026-11-03
- **Evidence**: `320956c` (repo-wide convention-based test runner), `42f0c8b` (recipe test runner into a private workspace package), `8e9a0c7` (all repository scripts into a TypeScript scripts package), `e9adfa5` (shell test harnesses → TypeScript node:test), `846d7b0` (remaining .mjs suites → TypeScript), `8f470fe` (workflow structure validation in the shared runner)

## Context

Each recipe carried its own test wiring: shell harnesses, bespoke `codemod jssg test` invocations, and per-package test commands that drifted. Repository tooling (gates, generators, release helpers) was scattered shell/`.mjs` scripts.

## Decision

`run-recipe-tests` (a bin from the private `@backstage/scripts` workspace package, declared as a devDependency — no relative paths) discovers suites by convention: `scripts/<name>.ts` + `tests/<name>/<case>/input.<ext>` run as JSSG suites with language inferred from the fixture extension; `tests/<name>/assert-fixtures.ts` runs whole-app trees through the recipe workflow; `tests/**/*.test.ts` runs under `node --test`; every `tests/**/tsconfig.json` is type-checked; workflow structure is validated (local `js_file`/`CODEMOD_SCRIPT` must exist, registry steps rejected). All repository tooling lives in `packages/scripts` as TypeScript bins with `node:test` regression tests (`yarn test:scripts`).

## Consequences

Adding a step never changes a test command, and shell/.mjs drift is gone. The runner itself is now the critical path: changes to discovery rules require updating its regression tests, and helpers need no same-stem fixture suite (tested through entrypoints or focused `node --test` suites).
17 changes: 17 additions & 0 deletions docs/adr/0005-release-owned-readme-and-versions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# ADR 0005: Release-owned README and version generation

- **Status**: Accepted
- **Date**: 2026-10-13 → 2026-11-03
- **Evidence**: `0fa47b2` (README sync to manual review PR), `5e9246b` (regenerate README on the Version Packages PR), `664f55a` (remove the redundant README-sync workflow), `8e52cb2` (regenerate the README inside the version-packages script), `c11b739` (pin the version-packages README stage in the wiring test)

## Context

The root README (codemod index) is generated, but syncing it drifted: a separate README-sync workflow caused churn, codemod PRs regenerated it inconsistently, and hand-edited `version` fields in `package.json`/`codemod.yaml` fought the Changesets bot.

## Decision

Changesets owns version bumps. The release workflow's `version-packages` script runs `changeset version && sync-codemod-versions && generate-readme-codemods` as one version commit on the **Version Packages** PR, so changeset consumption, version bumps, `codemod.yaml` sync, and the README always land together. There is no separate README-sync workflow. Codemod PRs leave the README unchanged; drift is regenerated on the next release PR or via `yarn readme` through a normal PR. The stage is pinned by a wiring test.

## Consequences

One place generates versions and README; no double-bookkeeping. The cost is that the README can lag `main` between releases (accepted), and manual version edits are always wrong (overwritten on the next Version Packages PR).
51 changes: 51 additions & 0 deletions docs/agents/domain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Domain Docs

How the engineering skills should consume this repo's domain documentation when exploring the codebase.

## Before exploring, read these

- **`GLOSSARY.md`** at the repo root, or
- **`GLOSSARY-MAP.md`** at the repo root if it exists: it points at one `GLOSSARY.md` per context. Read each one relevant to the topic.
- **`docs/adr/`**: read ADRs that touch the area you're about to work in. In multi-context repos, also check `src/<context>/docs/adr/` for context-scoped decisions.

If any of these files don't exist, **proceed silently**. Don't flag their absence; don't suggest creating them upfront. The `/domain-modeling` skill (reached via `/grill-with-docs` and `/improve-codebase-architecture`) creates them lazily when terms or decisions actually get resolved.

## File structure

Single-context repo (most repos):

```
/
├── GLOSSARY.md
├── docs/adr/
│ ├── 0001-event-sourced-orders.md
│ └── 0002-postgres-for-write-model.md
└── src/
```

Multi-context repo (presence of `GLOSSARY-MAP.md` at the root):

```
/
├── GLOSSARY-MAP.md
├── docs/adr/ ← system-wide decisions
└── src/
├── ordering/
│ ├── GLOSSARY.md
│ └── docs/adr/ ← context-specific decisions
└── billing/
├── GLOSSARY.md
└── docs/adr/
```

## Use the glossary's vocabulary

When your output names a domain concept (in an issue title, a refactor proposal, a hypothesis, a test name), use the term as defined in `GLOSSARY.md`. Don't drift to synonyms the glossary explicitly avoids.

If the concept you need isn't in the glossary yet, that's a signal: either you're inventing language the project doesn't use (reconsider) or there's a real gap (note it for `/domain-modeling`).

## Flag ADR conflicts

If your output contradicts an existing ADR, surface it explicitly rather than silently overriding:

> _Contradicts ADR-0007 (event-sourced orders), but worth reopening because…_
Loading
Loading