yandex/yatagan/yatagan-2-migration

Claude Code and agent skill by yandex/yatagan

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Use when upgrading a project from Yatagan 1.x to Yatagan 2.0 — bumping the dependency version, or hitting 2.0 errors such as "Missing binding" for a child component, @Provides(Conditional(...)) not compiling, setupReflectionBackend/setThreadAsserter missing, or Yatagan$ generated class names not found.

Install

npx vskill@latest install yandex/yatagan/yatagan-2-migration

SKILL.md

Yatagan 1.x → 2.0 Migration

Migrate a codebase from Yatagan 1.x (typically 1.6.2) to 2.0. Work through every step below; each is independent — skip a step only after its search (Step 0 inventory) proves the project has nothing to migrate for it.

Ground rules

  • Full cleanup. Deprecated-but-still-working Yatagan APIs (legacy @Condition family, api-compiled artifact) MUST be migrated too. Only warnings unrelated to Yatagan are out of scope.
  • Redirect search output to files. For every grep/rg, redirect raw output to a file and read the file (e.g. rg <pattern> > /tmp/yatagan-scan.txt 2>&1). Inline tool output may be truncated or rewritten by context-saving wrappers, producing wrong match counts. On very large repos rg can time out or silently produce an empty output file — if a search is slow or a result file is suspiciously empty, fall back to plain grep -r --include=... --exclude-dir=build and cross-check.
  • Distrust zero hits — and huge hit counts. If a search returns nothing, re-run with a simpler substring and different quoting before concluding the pattern is absent. In particular, mind $ escaping per tool: grep -F 'Yatagan$' (literal, no escape) or grep -E 'Yatagan\$' (escaped) — -F with \$ matches a literal backslash and silently finds nothing. Conversely, exclude build/, .gradle/, and other generated-output directories, or generated sources will drown the real matches.
  • Compiling is not evidence of cleanup. Yatagan 2.0 still ships the deprecated public symbols (@Condition family, Yatagan.setThreadAsserter, the ThreadAsserter type, api-compiled), so a green build does not prove steps 2b/2d/2i were done — only a zero-hit re-scan does. Internal machinery behind them may be gone, though (e.g. com.yandex.yatagan.internal.ThreadAssertions.setAsserter no longer exists) — profile/keep-rule entries naming internals are dead references to delete.
  • Build per repo rules. Find how this repository verifies a build (its own docs / agent rules — and check for project-specific CLI wrappers on PATH, not just Gradle/Maven invocations named in docs) and use that command for the baseline and final verification. A transient toolchain or build-daemon crash on a first/cold build is environmental — retry once before treating it as a migration failure.

Step 0 — Recon and baseline

  1. Locate the Yatagan version declaration (version catalog, dependency manifest, or build scripts) and all Yatagan artifact dependencies (api-public, api-compiled, api-dynamic, api-common, processor artifacts).
  2. Run the repo's build check to confirm a green baseline before changing anything.
  3. Inventory usages (redirect each to a file, then read the file):
  • @Provides( — conditional provides (step 2a)
  • @Condition(, @AllConditions(, @AnyCondition(, @AnyConditions( — legacy conditions (step 2b). Use these paren-anchored literals (with grep -F), not \b word boundaries — \b is a GNU extension that BSD grep may silently ignore. Also search for stale imports with an end-anchored pattern — grep -E 'yatagan\.(Condition|AllConditions|AnyCondition|AnyConditions)$' — or the search drowns in Conditional/Conditionals/ConditionExpression, which share the prefix. Beware the near-miss `@Conditionals` (repeatable container of @Conditional): it looks like the legacy @AnyConditions but is a current API and is NOT legacy — exclude it from 2b hits.
  • isRoot = false / component interfaces — child components (step 2c; triage per 2c, do not audit every injection site up front)
  • ThreadAsserter (the type — this also catches baseline-profile/keep-rule entries like Lcom/yandex/yatagan/ThreadAsserter; and ThreadAssertions;->setAsserter(...) that a setThreadAsserter search misses), plus setThreadAsserter and yatagan.experimental.omitThreadChecks (step 2d)
  • setupReflectionBackend, resetReflectionBackend, useCompiledImplementationIfAvailable (step 2e)
  • DynamicValidationDelegate implementations (step 2f)
  • SPI usage: ValidationPluginProvider, lang-api, core-model-api, core-graph-api, validation-api, validation-spi (step 2g)
  • Yatagan\$ in ProGuard/R8 rules, Class.forName, baseline profiles, serialized names, tooling (step 2h)
  • api-common, api-compiled dependencies (step 2i)
  • yatagan.experimental.reportDuplicateAliasesAsErrors processor option (step 2j)
  • Build logic: buildSrc/build-logic/convention plugins that set yatagan.* processor options, construct generated class names (string concatenation with Yatagan), or register bytecode transforms. Build tooling that hard-codes the 1.x naming scheme is the highest-risk find — and it may sit behind a default-off build flag, so a green default build proves nothing about it (see Step 3). Record the names and invocation syntax of any build flags gating Yatagan-related code paths (e.g. -Pflag=true, env vars) — Step 3 needs them to verify coverage.

Step 1 — Bump the version

Set the Yatagan version to 2.0.0 wherever it is declared.

Step 2 — Code migrations

2a. @Provides no longer accepts Conditional arguments

@Provides is a plain marker in 2.0. Move each Conditional(...) argument out into a separate @Conditional(...) annotation on the same method:

kotlin
// 1.x
@Provides(Conditional(MyFeature::class))
fun provideImpl(): MyApi = MyApiImpl()
// 2.0
@Provides
@Conditional(MyFeature::class)
fun provideImpl(): MyApi = MyApiImpl()

Watch for all argument forms: positional @Provides(Conditional(A::class)), multiple @Provides(Conditional(A::class), Conditional(B::class)) (becomes repeated @Conditional annotations), and named/array @Provides(value = [Conditional(A::class)]) (Java uses {...}). Inside @Provides(...) you may also find a Conditionals(...) container argument — handle it here; this is unrelated to the class-level @Conditionals annotation, which is unchanged and must not be touched. Arguments may span multiple lines — rewrite by matching parentheses, not by line.

Formatting after the rewrite — check every rewritten site against the project's per-language line-length limits (.editorconfig limits often differ between Java and Kotlin):

  • extraction lengthens lines even without collapsing (the added @ can push a previously-legal line over the limit);
  • a hoisted multi-line argument was nested one level deeper inside @Provides(...) — dedent its continuation lines and closing parenthesis by one level.

onlyIn = [...] and repeated @Conditional work the same on methods as on classes — in 2.0 @Conditional is repeatable (@JvmRepeatable(Conditionals::class)) and targets functions and property getters, so the repeated-annotation form is legal in both Kotlin and Java. @Conditional on classes is unchanged — no migration.

2b. Legacy conditions → @ConditionExpression

@Condition, @AllConditions, @AnyCondition, @AnyConditions are deprecated. Replace with @ConditionExpression:

kotlin
// legacy
@Condition(Features::class, condition = "isAliceProEnabled")
annotation class AliceProEnabled
// modern
@ConditionExpression("isAliceProEnabled", Features::class)
annotation class AliceProEnabled

@ConditionExpression supports full boolean expressions — &, |, !, feature references (@OtherFeature), multiple imports with optional importAs aliases — so repeated @Condition/@AnyCondition stacking (CNF) collapses into one expression:

kotlin
// legacy
@AnyCondition(
Condition(Features::class, "isA"),
Condition(Features::class, "isB"),
)
annotation class AOrB
// modern
@ConditionExpression("isA | isB", Features::class)
annotation class AOrB

The legacy condition string is already valid @ConditionExpression syntax, including the ! negation prefix and dotted access paths — copy it verbatim; do not re-derive getter/INSTANCE spellings that were already correct in 1.x. (For new expressions: access paths use Java-view names — property getters as getFoo, Kotlin objects via INSTANCE, companions via Companion.)

Reference syntax rules: the short (unqualified) form ("isAliceProEnabled") is legal only when the annotation has exactly one import; with two or more imports every variable must use the qualified form Cls::path (or an importAs alias). Since a legacy @Condition only ever named one class, every mechanically-converted site has exactly one import — prefer the short positional form @ConditionExpression("expr", X::class): it is legal there and length-neutral with the legacy annotation, whereas the qualified form ("X::expr") and the named form (imports = [X::class]) each add characters that can push hundreds of single-line sites over the limit. Decide the form (and check the project's per-language line-length limits, as in 2a) before rewriting a large batch. Non-static providers are resolved from the graph, same as 1.x. @ConditionExpression exists in 1.6.2 with the same syntax, so this step can't break a 1.x build.

2c. Direct injection of child components is forbidden

Injecting a child (non-root) component instance into parent-graph bindings no longer works — it produces Missing binding for ... with the note "A dependency seems to be a child component, try injecting its factory instead."

Triage first — most components need no work. For each non-root component, check whether it declares its own @Component.Builder/@Component.Factory: those are already in the 2.0 shape, skip them. Audit only:

  1. children without an explicit builder/factory;
  2. parent-side abstract methods returning a child component type (now factory methods — see below);
  3. bindings or @Inject constructors taking a child component type (including Lazy<Child>/Provider<Child>) as a parameter.

Expect categories 2 and 3 to be dominated by false positives; dismissal rules:

  • methods returning Child.Builder/Child.Factory are already the 2.0 shape — a builder return type is not a component return type;
  • @BindsInstance parameters of a child component type are legal (the instance is supplied externally, nothing is missing);
  • @Binds methods inside the child's own modules — a component binding itself into its own graph is still legal;
  • plain non-DI code that merely mentions the type (tests, mocks, utilities).

Keep this audit at grep level — do not deep-inspect thousands of candidates. Step 3's build is the backstop: any missed site fails loudly with Missing binding ... seems to be a child component. For real direct-injection sites, the fix is to inject the child's builder and create the instance lazily:

kotlin
// 2.0
@Component(isRoot = false)
interface SettingsComponent {
/* entry points */
@Component.Builder
interface Builder { fun create(): SettingsComponent }
}
class SettingsRouter @Inject constructor(
private val settingsBuilder: SettingsComponent.Builder,
) {
private val settings by lazy { settingsBuilder.create() }
}

Also: every abstract method of a component interface that returns a non-root component is now a factory method (in 1.x, parameterless ones were entry points). A child must expose either an explicit @Component.Builder or factory methods in its parent — both together is an error ("Child components can't have a factory methods declared for them in their parents…"). If the child has an explicit builder, remove parent-side factory methods (or drop the builder).

2d. Yatagan.setThreadAsserter() → build-time thread checker

setThreadAsserter(...) is a deprecated (ERROR level) no-op. Replace each asserter with a checker class and a processor option:

kotlin
object MainThreadChecker {
@JvmStatic
fun assertThreadAccess() {
// port the OLD asserter's body here verbatim
}
}

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
0

Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/yandex/yatagan/yatagan-2-migration/badge)](https://verified-skill.com/skills/yandex/yatagan/yatagan-2-migration)

Something wrong with this skill or its page? Tell us.