You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
# ADR-28080: Multi-Path Serena Config Discovery and Structural YAML Mutation in Codemod
2
+
3
+
**Date**: 2026-04-23
4
+
**Status**: Draft
5
+
**Deciders**: pelikhan
6
+
7
+
---
8
+
9
+
## Part 1 — Narrative (Human-Friendly)
10
+
11
+
### Context
12
+
13
+
The `serena-tools-to-shared-import` codemod automates migration of inline Serena tool configuration to the shared `shared/mcp/serena.md` import. The original implementation only detected Serena config at the top-level `tools.serena` YAML path. However, workflows that specify a custom LLM engine can place the same config under `engine.tools.serena` instead. When such workflows are processed by the old codemod, the migration is silently skipped, leaving the workflow non-compilable after the shared import becomes mandatory. A second compounding issue: workflows pinned to a specific `github/gh-aw` commit SHA via `source:` reference an older version of the import chain that predates the required `with.languages` input, so even a successfully migrated workflow would fail validation at the pinned version.
14
+
15
+
### Decision
16
+
17
+
We will extend `findSerenaLanguagesForMigration` to probe both `tools.serena` (top-level) and `engine.tools.serena` (nested under an engine block), treating whichever location is populated as the migration source. We will fix YAML block insertion to scan forward to the end of the entire `engine` block before inserting the new `imports` entry, preserving sibling engine fields such as `model` and `id`. We will replace the simple top-level block removal with an indentation-aware `removeBlockIfEmpty` that avoids deleting engine blocks that retain meaningful sibling content. Finally, we will add `maybeUpdatePinnedSourceRef`, which rewrites any `source:` value pointing to `github/gh-aw` at a 40-character commit SHA to `@main` during the same migration pass, preventing stale-import validation failures.
18
+
19
+
### Alternatives Considered
20
+
21
+
#### Alternative 1: Separate Codemods per YAML Path
22
+
23
+
Define a second, independent codemod that handles `engine.tools.serena` exclusively, leaving the original `tools.serena` codemod unchanged. This keeps each codemod small and single-purpose, but requires both to be applied in the correct order and makes it easy for a caller to apply only one — leaving workflows in a half-migrated state that is harder to debug than no migration at all.
24
+
25
+
#### Alternative 2: Require Manual Migration for Engine-Scoped Serena Config
26
+
27
+
Document that `engine.tools.serena` requires manual migration and skip automation entirely. This is low risk for the codemod itself but shifts toil onto every workflow author whose workflow uses this pattern, and provides no protection against the `source:` pin problem. Given the volume of affected workflows in the repository, manual migration was not a viable path.
28
+
29
+
### Consequences
30
+
31
+
#### Positive
32
+
- Workflows using `engine.tools.serena` are now migrated automatically, closing a silent failure mode.
33
+
- The `@main` source pin rewrite prevents post-migration validation failures against stale upstream import chains that lack the now-required `with.languages` input.
34
+
- Indentation-aware block removal preserves `engine` sibling fields (`id`, `model`) at the correct YAML depth, making the transformation structurally correct for the full range of engine block shapes.
35
+
36
+
#### Negative
37
+
-`findSerenaLanguagesForMigration` now encodes a precedence rule (top-level `tools.serena` wins over `engine.tools.serena`); if a workflow has both, only the top-level value is used and the engine-scoped config is silently discarded.
38
+
- The `maybeUpdatePinnedSourceRef` rewrite is scoped to `github/gh-aw` sources pinned by commit SHA — workflows pinned to forks, tags, or other repos are unaffected, requiring separate handling if the same pin-staleness problem arises there.
39
+
- Indentation-aware YAML mutation increases the complexity of `removeBlockIfEmpty` and `hasNestedContent`, making edge cases harder to reason about without a property-based test suite.
40
+
41
+
#### Neutral
42
+
- The change touches only `pkg/cli/codemod_serena_import.go`, keeping scope narrow and the diff reviewable in a single pass.
43
+
- Regression tests added for both `engine.tools.serena` migration with sibling preservation and the `source:` SHA rewrite path.
44
+
45
+
---
46
+
47
+
## Part 2 — Normative Specification (RFC 2119)
48
+
49
+
> The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHALL**, **SHALL NOT**, **SHOULD**, **SHOULD NOT**, **RECOMMENDED**, **MAY**, and **OPTIONAL** in this section are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119).
50
+
51
+
### Serena Config Discovery
52
+
53
+
1. Implementations **MUST** detect Serena configuration at `tools.serena` (top-level) before checking `engine.tools.serena`.
54
+
2. Implementations **MUST** fall back to `engine.tools.serena` when no `tools.serena` key is present in the frontmatter.
55
+
3. Implementations **MUST NOT** attempt migration when neither location contains a non-empty `languages` list.
56
+
4. Implementations **MUST NOT** merge languages from both paths; the first matching path **SHALL** be the sole source of the language list.
57
+
58
+
### YAML Block Insertion
59
+
60
+
1. When inserting the `imports` block adjacent to an `engine:` block, implementations **MUST** scan forward to the end of the full `engine` block (i.e., until the next top-level key or end-of-document) before computing the insertion point.
61
+
2. Implementations **MUST NOT** insert the `imports` block on the line immediately following the `engine:` key, as this would interleave the new block with the engine block's nested content.
62
+
63
+
### Empty-Block Removal
64
+
65
+
1. Implementations **MUST** remove a YAML block (`tools`, `engine`) only when it contains no meaningful nested content after migration — where "meaningful" means a non-empty, non-comment child line indented deeper than the block key.
66
+
2. Implementations **MUST** preserve the enclosing block (e.g., `engine:`) when sibling fields remain after the migrated sub-key (`tools`) is removed.
67
+
3. Implementations **MUST NOT** remove a block based solely on indentation depth; the check **MUST** inspect actual content.
68
+
69
+
### Pinned Source Ref Rewrite
70
+
71
+
1. When a migration is applied and `source:` is present in frontmatter, implementations **MUST** rewrite the ref to `@main` if and only if: (a) the source repo is `github/gh-aw`, and (b) the current ref is exactly a 40-character hexadecimal commit SHA.
72
+
2. Implementations **MUST NOT** rewrite `source:` values that point to a different repository, use a named branch, or use a tag.
73
+
3. Implementations **SHOULD** perform the source ref rewrite in the same migration pass as the `tools.serena` removal to keep the workflow in a consistent state after a single codemod run.
74
+
75
+
### Conformance
76
+
77
+
An implementation is considered conformant with this ADR if it satisfies all **MUST** and **MUST NOT** requirements above. Failure to meet any **MUST** or **MUST NOT** requirement constitutes non-conformance.
78
+
79
+
---
80
+
81
+
*This is a DRAFT ADR generated by the [Design Decision Gate](https://github.com/github/gh-aw/actions/runs/24840177385) workflow. The PR author must review, complete, and finalize this document before the PR can merge.*
// getSerenaToSharedImportCodemod creates a codemod that migrates removed tools.serena
16
-
// configuration to an equivalent imports entry using shared/mcp/serena.md.
16
+
// or engine.tools.serena configuration to an equivalent imports entry using
17
+
// shared/mcp/serena.md, and may normalize a pinned source ref to @main.
17
18
funcgetSerenaToSharedImportCodemod() Codemod {
18
19
returnCodemod{
19
20
ID: "serena-tools-to-shared-import",
20
-
Name: "Migrate tools.serena to shared Serena import",
21
-
Description: "Removes 'tools.serena' and adds an equivalent 'imports' entry using shared/mcp/serena.md with languages.",
21
+
Name: "Migrate tools.serena or engine.tools.serena to shared Serena import",
22
+
Description: "Removes 'tools.serena' or 'engine.tools.serena', adds an equivalent 'imports' entry using shared/mcp/serena.md with languages, and may rewrite a pinned 'source:' ref to '@main'.",
serenaImportCodemodLog.Print("Found tools.serena but languages configuration is invalid or empty - skipping migration; verify tools.serena languages are set")
0 commit comments