fix(sync-dirs): self-heal libs.versions.toml when upstream adds build-logic aliases#163
Conversation
Every `libs.<name>` accessor inside `build-logic/` must resolve to an alias
declared in `gradle/libs.versions.toml`. When `build-logic/` is auto-synced into
a consumer fork via the `sync-dirs.yaml` workflow but the matching version-catalog
entries are NOT (because `gradle/libs.versions.toml` is intentionally project-
local — it carries the consumer's package namespace, version overrides, etc.),
the synced build-logic ends up referencing aliases that don't exist in the
consumer's catalog. Result: silent broken state that only surfaces on the first
build attempt against the synced state, hours to weeks after the bad sync ran.
This change adds a fast PR-time gate that catches the gap immediately. Two files:
- `scripts/verify_libs_resolved.sh` — greps every `libs.<name>` reference inside
`build-logic/`, normalises each to its expected TOML alias key per Gradle's
version-catalog accessor convention (dots → hyphens), and verifies the key is
declared in `gradle/libs.versions.toml`. Handles namespace prefixes correctly:
libs.<a> → [libraries] key `<a>` (dots → hyphens)
libs.plugins.<a> → [plugins] key `<a>`
libs.versions.<a> → [versions] key `<a>`
Skips method-call accessors that take string args, not property paths:
libs.findLibrary("…"), libs.findVersion("…"), libs.findBundle("…"),
libs.findPlugin("…").
Also whitelists `libs.versions.toml` as a string-literal reference to the
catalog file name itself.
- `.github/workflows/verify-libs-resolved.yml` — invokes the script on every PR
that touches `build-logic/**`, `gradle/libs.versions.toml`, or the gate itself.
No Gradle setup needed — pure bash gate, runs in seconds.
Both files ship to consumer forks via `sync-dirs.yaml` (`.github/` + `scripts/`
are in the default sync list), so every fork picks up the same guarantee with
zero per-fork configuration.
Verified locally:
- Template current state: 17 libs.* refs all resolve → ✅ exit 0.
- Field-officer pre-fix simulation (libs.versions.toml at the broken state from
~24h ago, before manual catch-up): catches all 5 unresolved refs including
`libs.plugins.kover.convention → kover-convention` in [plugins] section, exit 1.
This is the recurring class of failure that's been hand-fixed at least 3 times in
4 months on the field-officer fork:
- #2572 (2026-01-30) chore(build-logic): add missing dependencies to version
catalog & missing build logic files
- #2604 (2026-02-18) chore: update versions parity with kmp-project-template
- Manual catch-up after #2682 sync (2026-05-21) → fixed today on field-officer
The gate makes the failure mode impossible to silently re-occur on this template
and on any fork that consumes the synced `.github/` + `scripts/` directories.
📝 WalkthroughWalkthroughThis PR extends the ChangesSelf-healing sync for missing Gradle catalog entries
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
.github/workflows/verify-libs-resolved.yml (1)
48-49: ⚡ Quick winAdd security hardening to checkout action.
The checkout step should disable credential persistence to prevent token leakage. Additionally, consider pinning the action to a specific commit hash instead of a tag for supply chain security.
🔒 Proposed security improvements
- name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/verify-libs-resolved.yml around lines 48 - 49, Update the Checkout step (currently using "uses: actions/checkout@v4") to harden security by adding "persist-credentials: false" under its "with:" block to prevent token credential persistence, and replace the tag-based reference "actions/checkout@v4" with a pinned commit SHA (actions/checkout@<commit-sha>) to lock the action to a specific commit for supply-chain security; keep other settings (e.g., fetch-depth) unchanged unless you intentionally need shallow fetch.scripts/verify_libs_resolved.sh (1)
94-94: 💤 Low valueConsider using bash parameter expansion instead of sed.
The dot-to-hyphen conversion can be done more efficiently with bash built-in parameter expansion.
⚡ Proposed optimization
- key=$(echo "$ref" | sed 's/\./-/g') + key="${ref//./-}"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/verify_libs_resolved.sh` at line 94, The current assignment to key uses an external sed call to replace dots with hyphens; replace that sed pipeline in the key assignment with bash's built-in parameter expansion to perform an in-shell global substitution on the variable ref (i.e., use the parameter-expansion form that replaces all '.' with '-') so you avoid spawning sed and improve performance — update the line assigning key (which references ref) accordingly and ensure the script has a bash-compatible shebang.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/verify_libs_resolved.sh`:
- Around line 43-53: Add a prerequisite-check block near the top (around where
REPO_ROOT is determined) that verifies required commands are present before
proceeding: at minimum validate git, grep, sed plus the other mandated tools
(gh, firebase, keytool, openssl, python3); if any are missing, print a clear
error and exit non‑zero. Use the same pattern used for git detection (command -v
...) and reference the REPO_ROOT initialization context so failures occur before
using LIBS_TOML or BUILD_LOGIC.
---
Nitpick comments:
In @.github/workflows/verify-libs-resolved.yml:
- Around line 48-49: Update the Checkout step (currently using "uses:
actions/checkout@v4") to harden security by adding "persist-credentials: false"
under its "with:" block to prevent token credential persistence, and replace the
tag-based reference "actions/checkout@v4" with a pinned commit SHA
(actions/checkout@<commit-sha>) to lock the action to a specific commit for
supply-chain security; keep other settings (e.g., fetch-depth) unchanged unless
you intentionally need shallow fetch.
In `@scripts/verify_libs_resolved.sh`:
- Line 94: The current assignment to key uses an external sed call to replace
dots with hyphens; replace that sed pipeline in the key assignment with bash's
built-in parameter expansion to perform an in-shell global substitution on the
variable ref (i.e., use the parameter-expansion form that replaces all '.' with
'-') so you avoid spawning sed and improve performance — update the line
assigning key (which references ref) accordingly and ensure the script has a
bash-compatible shebang.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8bae9098-cce2-4b5c-a97a-302422cd1756
📒 Files selected for processing (2)
.github/workflows/verify-libs-resolved.ymlscripts/verify_libs_resolved.sh
| # Resolve repo root: git top-level is the truth when available (handles CI checkout | ||
| # layouts + the case where this script is sync'd into a consumer fork and runs | ||
| # against the CONSUMER's catalog, not the template's). Falls back to CWD for | ||
| # environments without git (very rare). | ||
| if command -v git >/dev/null 2>&1 && REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"; then | ||
| : | ||
| else | ||
| REPO_ROOT="$(pwd)" | ||
| fi | ||
| LIBS_TOML="$REPO_ROOT/gradle/libs.versions.toml" | ||
| BUILD_LOGIC="$REPO_ROOT/build-logic" |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Validate all required prerequisites per coding guidelines.
The script validates git availability but also depends on grep and sed commands that are used later without validation. As per coding guidelines, bash scripts must validate prerequisites before executing main logic.
✅ Proposed prerequisite validation
set -uo pipefail
+# Validate prerequisites
+for cmd in grep sed; do
+ if ! command -v "$cmd" >/dev/null 2>&1; then
+ echo "❌ Required command '$cmd' not found. Please install it and try again." >&2
+ exit 1
+ fi
+done
+
# Resolve repo root: git top-level is the truth when available (handles CI checkout
# layouts + the case where this script is sync'd into a consumer fork and runs
# against the CONSUMER's catalog, not the template's). Falls back to CWD for
# environments without git (very rare).As per coding guidelines, Bash scripts must validate prerequisites (git, gh, firebase, keytool, openssl, python3) before executing main logic.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/verify_libs_resolved.sh` around lines 43 - 53, Add a
prerequisite-check block near the top (around where REPO_ROOT is determined)
that verifies required commands are present before proceeding: at minimum
validate git, grep, sed plus the other mandated tools (gh, firebase, keytool,
openssl, python3); if any are missing, print a clear error and exit non‑zero.
Use the same pattern used for git detection (command -v ...) and reference the
REPO_ROOT initialization context so failures occur before using LIBS_TOML or
BUILD_LOGIC.
…ript) — no new files Per review feedback: the right place for this fix is the sync-dirs flow itself, not a separate workflow that runs after-the-fact. Removed the dedicated workflow + companion script from the previous commit; integrated the logic inline into both .github/workflows/sync-dirs.yaml and sync-dirs.sh. What changed vs the previous commit on this branch (ceb1a79): - DELETED .github/workflows/verify-libs-resolved.yml (-52 lines) - DELETED scripts/verify_libs_resolved.sh (-180 lines) + .github/workflows/sync-dirs.yaml — new step "Heal libs.versions.toml" between "Sync directories and files" and "Clean up temporary branch". Runs the same detection logic, but instead of just failing the gate, it AUTO-PULLS the missing alias entries (and any version.ref keys they depend on) directly from the upstream catalog accessible via $TEMP_BRANCH. The healed catalog is then committed as part of the same sync PR — no separate fix-up PR ever needed. + sync-dirs.sh — new heal_libs_versions_toml() function invoked between the sync loop and temp-branch cleanup. Same heal semantics for local CLI use. The downstream `git add` is widened to include gradle/libs.versions.toml when the heal touched it. Behavior: 1. Sync-dirs pulls build-logic from upstream (existing behavior, unchanged) 2. Heal step inspects every libs.<X> reference in the freshly-synced build-logic 3. For each ref whose alias is missing from consumer's gradle/libs.versions.toml, pull the matching line from upstream (TEMP_BRANCH) and insert it into the appropriate [versions] / [libraries] / [plugins] section 4. If an alias version.refs an undeclared version key, that version key is also pulled and inserted into [versions] BEFORE the alias 5. Healed entries land in the same sync PR — discovery + remediation in one atomic workflow run, instead of a "fails-now-fix-tomorrow" split Verified against field-officer's pre-fix state (catalog reverted to commit 408d9cba — the broken state from ~24h ago, before the manual #2691 catch-up): - Pre-heal: 5 missing aliases — kover-gradlePlugin, kover-convention, androidx-room-gradle-plugin, kmp-product-flavors-plugin, spotless-gradle - Post-heal: 7 entries inserted (5 aliases + 2 referenced versions kmpProductFlavors, kover). All build-logic libs.<X> refs resolve. Eliminates the failure mode that's been hand-fixed at least 3 times in 4 months on the field-officer fork (openMF/mifos-x-field-officer-app#2572, #2604, #2691).
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
sync-dirs.sh (1)
681-688:⚠️ Potential issue | 🟠 Major | ⚡ Quick winStage the healed catalog before checking for changes.
heal_libs_versions_toml()only updates the working tree. If a run only needsgradle/libs.versions.tomlhealing,git diff --cachedis still clean here, so the script drops the branch and loses the fix.Suggested fix
- # Check for changes - if ! git diff --quiet --exit-code --cached; then - print_step "Committing changes..." - git add "${SYNC_DIRS[@]}" "${SYNC_FILES[@]}" - # Include any catalog heal additions made by heal_libs_versions_toml. - if [ -f "gradle/libs.versions.toml" ]; then - git add "gradle/libs.versions.toml" - fi + git add "${SYNC_DIRS[@]}" "${SYNC_FILES[@]}" + if [ -f "gradle/libs.versions.toml" ]; then + git add "gradle/libs.versions.toml" + fi + + # Check for changes + if ! git diff --quiet --exit-code --cached; then + print_step "Committing changes..."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@sync-dirs.sh` around lines 681 - 688, The change-detection uses "git diff --quiet --exit-code --cached" before staging the healed catalog, so a run that only modifies gradle/libs.versions.toml via heal_libs_versions_toml() will be missed; move or add a staging step for that file before the cached-diff check: after calling heal_libs_versions_toml() (or before the existing "if ! git diff ... --cached" block) detect if "gradle/libs.versions.toml" exists and git add it (same as the later add), ensuring the healed catalog is staged prior to the git diff --cached check so the script will see changes and commit them.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/sync-dirs.yaml:
- Around line 332-347: The current grep checks for "^${key}=" across the whole
TOML which can match identical keys in other sections; instead, determine which
TOML section the key belongs to (e.g., libraries/plugins/versions) and extract
only that section from both "$LIBS_TOML" and "$upstream_toml" before running the
greps. Concretely: compute a section name for the current key (based on how key
is built for libs.$ref), extract section_local=$(sed -n
'/^\[<section>\]/,/^\[/{p}' "$LIBS_TOML") and section_upstream=$(echo
"$upstream_toml" | sed -n '/^\[<section>\]/,/^\[/{p}'), then replace the three
greps that use "$LIBS_TOML" or "$upstream_toml" (the exists check, upstream_line
lookup, and ref_version presence check that sets ver_line) to search only within
section_local or section_upstream as appropriate (and when ref_version refers to
a version key, search the [versions] section contents). Ensure variable names
from the diff (key, LIBS_TOML, upstream_toml, ref_version, ver_line) are used so
you update the exact checks.
In `@sync-dirs.sh`:
- Around line 624-641: The current grep checks on $libs_toml and $upstream_toml
are scanning entire files and can match keys from other TOML sections; update
the lookup logic so both the alias lookup (when computing $upstream_line) and
the version lookup (when computing $ver_line using $ref_version) first extract
the specific TOML section block (e.g. the computed libraries/plugins section
that contains the alias) and then run the grep on that block only. Concretely:
derive the section header for the alias, extract the block from $libs_toml and
$upstream_toml (using sed/awk to get lines between the header and the next
[...]) into temporary variables and use those variables in the existing
grep/grep -oE commands that produce $upstream_line, $ref_version and $ver_line
so matches are section-scoped.
---
Outside diff comments:
In `@sync-dirs.sh`:
- Around line 681-688: The change-detection uses "git diff --quiet --exit-code
--cached" before staging the healed catalog, so a run that only modifies
gradle/libs.versions.toml via heal_libs_versions_toml() will be missed; move or
add a staging step for that file before the cached-diff check: after calling
heal_libs_versions_toml() (or before the existing "if ! git diff ... --cached"
block) detect if "gradle/libs.versions.toml" exists and git add it (same as the
later add), ensuring the healed catalog is staged prior to the git diff --cached
check so the script will see changes and commit them.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 61ce282f-dcec-4f41-b627-9b3fec76ff0e
📒 Files selected for processing (2)
.github/workflows/sync-dirs.yamlsync-dirs.sh
| if grep -qE "^${key}[[:space:]]*=" "$LIBS_TOML"; then | ||
| continue | ||
| fi | ||
|
|
||
| # Pull the matching entry from upstream. | ||
| upstream_line=$(echo "$upstream_toml" | grep -E "^${key}[[:space:]]*=" | head -1) | ||
| if [[ -z "$upstream_line" ]]; then | ||
| echo " ⚠️ Missing alias '$key' (for libs.$ref) not present in upstream catalog either — manual fix needed." | ||
| warnings=$((warnings + 1)) | ||
| continue | ||
| fi | ||
|
|
||
| # If the alias references a version.ref, ensure that version key is also present. | ||
| ref_version=$(echo "$upstream_line" | grep -oE 'version\.ref = "[^"]+"' | sed -E 's/version\.ref = "([^"]+)"/\1/' | head -1) | ||
| if [[ -n "$ref_version" ]] && ! grep -qE "^${ref_version}[[:space:]]*=" "$LIBS_TOML"; then | ||
| ver_line=$(echo "$upstream_toml" | grep -E "^${ref_version}[[:space:]]*=" | head -1) |
There was a problem hiding this comment.
Make the workflow's catalog matching section-aware.
This step also searches the entire TOML for ^${key}=. If the same key exists in another section, the workflow can incorrectly skip a missing alias or fetch the wrong upstream line. Match within the resolved [libraries], [plugins], or [versions] block instead.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/sync-dirs.yaml around lines 332 - 347, The current grep
checks for "^${key}=" across the whole TOML which can match identical keys in
other sections; instead, determine which TOML section the key belongs to (e.g.,
libraries/plugins/versions) and extract only that section from both "$LIBS_TOML"
and "$upstream_toml" before running the greps. Concretely: compute a section
name for the current key (based on how key is built for libs.$ref), extract
section_local=$(sed -n '/^\[<section>\]/,/^\[/{p}' "$LIBS_TOML") and
section_upstream=$(echo "$upstream_toml" | sed -n '/^\[<section>\]/,/^\[/{p}'),
then replace the three greps that use "$LIBS_TOML" or "$upstream_toml" (the
exists check, upstream_line lookup, and ref_version presence check that sets
ver_line) to search only within section_local or section_upstream as appropriate
(and when ref_version refers to a version key, search the [versions] section
contents). Ensure variable names from the diff (key, LIBS_TOML, upstream_toml,
ref_version, ver_line) are used so you update the exact checks.
| if grep -qE "^${key}[[:space:]]*=" "$libs_toml"; then | ||
| continue | ||
| fi | ||
|
|
||
| local upstream_line | ||
| upstream_line=$(echo "$upstream_toml" | grep -E "^${key}[[:space:]]*=" | head -1) | ||
| if [ -z "$upstream_line" ]; then | ||
| print_warning "Missing alias '$key' (for libs.$ref) not present in upstream catalog either — manual fix needed." | ||
| warnings=$((warnings + 1)) | ||
| continue | ||
| fi | ||
|
|
||
| # Pull the version key too, if the alias version.refs something missing. | ||
| local ref_version | ||
| ref_version=$(echo "$upstream_line" | grep -oE 'version\.ref = "[^"]+"' | sed -E 's/version\.ref = "([^"]+)"/\1/' | head -1) | ||
| if [ -n "$ref_version" ] && ! grep -qE "^${ref_version}[[:space:]]*=" "$libs_toml"; then | ||
| local ver_line | ||
| ver_line=$(echo "$upstream_toml" | grep -E "^${ref_version}[[:space:]]*=" | head -1) |
There was a problem hiding this comment.
Scope catalog lookups to the computed TOML section.
These grep checks scan the whole file, so a key that already exists in [versions] can make a missing [plugins] or [libraries] entry look resolved, and the upstream lookup can copy the wrong line for the same reason. The heal logic needs section-aware matching for both the alias lookup and the version.ref lookup.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@sync-dirs.sh` around lines 624 - 641, The current grep checks on $libs_toml
and $upstream_toml are scanning entire files and can match keys from other TOML
sections; update the lookup logic so both the alias lookup (when computing
$upstream_line) and the version lookup (when computing $ver_line using
$ref_version) first extract the specific TOML section block (e.g. the computed
libraries/plugins section that contains the alias) and then run the grep on that
block only. Concretely: derive the section header for the alias, extract the
block from $libs_toml and $upstream_toml (using sed/awk to get lines between the
header and the next [...]) into temporary variables and use those variables in
the existing grep/grep -oE commands that produce $upstream_line, $ref_version
and $ver_line so matches are section-scoped.
…adle script compilation CI failure on PR openMF#2691 commit 463426d (Test Coverage Floor workflow): script compilation errors in cmp-android/build.gradle.kts — unresolved `libs.plugins. android.application.convention`, `libs.plugins.android.application.compose. convention`, `libs.plugins.aboutLibraries`. Root cause: build.gradle.kts files across the project (cmp-android, core-base/*, libs/*) were sync'd from upstream template via sync-dirs.yaml but the matching catalog entries in gradle/libs. versions.toml weren't included in the sync (the toml is intentionally consumer- local — it carries the project's package namespaces + version overrides). This is the SAME failure mode that openMF/kmp-project-template#163 fixes upstream by making sync-dirs self-healing. Until that PR merges + the next sync-dirs run propagates the heal logic, field-officer needs a manual catch-up of every catalog entry referenced by sync'd build.gradle.kts files. Audit: project-wide grep of every `libs.<X>` reference in `**/*.kts` + `**/*.kt` against gradle/libs.versions.toml found 16 unresolved aliases (180 total refs scanned, 16 missing). Added to [versions]: kmptoolkit = "3.2.3" aboutLibraries = "13.2.1" androidxHiltNavigationCompose = "1.2.0" androidxSecurityCrypto = "1.1.0-alpha06" bouncycastle = "1.78.1" sqliteWeb = "2.6.2" store = "5.1.0-alpha08" turbine = "1.2.1" Added to [libraries]: aboutlibraries-core, aboutlibraries-compose-core, aboutlibraries-compose-m3 androidx-hilt-navigation-compose androidx-security-crypto androidx-sqlite-web androidx-test-ext-junit (uses existing junitVersion = "1.2.1" which is actually the ext-junit version, not junit4) bouncycastle cmp-network-monitor + cmp-network-monitor-compose (both use kmptoolkit version) junit (uses existing junit4 = "4.13.2" — separate from junitVersion) kermit-koin store5 + store5-cache turbine Added to [plugins]: aboutLibraries = { id = "com.mikepenz.aboutlibraries.plugin", … } android-application-convention = { id = "org.convention.android.application" } android-application-compose-convention = { id = "org.convention.android.application.compose" } The 3 plugin convention aliases match plugin IDs registered in build-logic/convention/build.gradle.kts — the registration was already there; only the alias entry was missing. Verification: full re-run of the libs-resolution audit (the exact algorithm that the upstream PR openMF#163 ships as a sync-dirs heal step) now reports 0 unresolved references project-wide: scanned: 188 libs.* refs missing: 0 Categories of the 188 refs: - 14 in build-logic/convention/build.gradle.kts (fixed in commit 4077e85) - ~50 in cmp-android, cmp-shared, cmp-navigation, cmp-desktop, cmp-web, cmp-ios - ~80 in core-base/*, libs/* (vendored modules sync'd from template) - rest in feature/*, core/* This unblocks the next CI run on PR openMF#2691. Expected outcome: kover coverage job progresses past script compilation; surfaces whatever non-catalog compile issues remain (likely a smaller surface from B4/B6/B6.10 cross-tier moves).
Summary
Makes
sync-dirs.yaml+sync-dirs.shself-healing: when upstream adds newlibs.<X>accessors tobuild-logic/along with their matching catalog entries, sync-dirs now also pulls the matchinggradle/libs.versions.tomlaliases (and any version-keys they reference) into the consumer fork — all in the same sync PR.The bug class this fixes
build-logic/is insync-dirs's defaultDIRS=(...)list — every consumer fork pulls the latest template build-logic on the Monday cron (orworkflow_dispatch).But
gradle/libs.versions.tomlis intentionally not in the sync list: it carries project-local data (package namespaces, version overrides, project-specific lib pins). When upstream adds newlibs.<X>accessors in build-logic with their matching alias entries in the catalog (e.g. PR #160 addedlibs.plugins.kover.convention), sync-dirs brings the build-logic over but leaves the consumer's catalog behind. Result: silent broken state — gradle won't complain until the alias is actually evaluated by a downstream build, hours to weeks after the bad sync ran.This has been hand-fixed at least 3 times in 4 months on the field-officer fork:
openMF/mifos-x-field-officer-app#2572(2026-01-30) — chore(build-logic): add missing dependencies to version catalog & missing build logic filesopenMF/mifos-x-field-officer-app#2604(2026-02-18) — chore: update versions parity with kmp-project-templateopenMF/mifos-x-field-officer-app#2691(2026-05-22) — manual catch-up of 5 missing aliases (kover-gradlePlugin,kover-convention,androidx-room-gradle-plugin,kmp-product-flavors-plugin,spotless-gradle) after the PR refactor(core): consolidate AppStoreRegistry on core/store + Room alpha05 #160 + refactor(core/database): lift ChargeTypeConverters.install to commonMain #161 + fix(core/database): detekt MatchingDeclarationName on ChargeTypeConvertersInstalled #162 syncsWhat this PR ships
Both sync-dirs entry points get the same heal logic inlined directly — no new workflow files, no new scripts.
.github/workflows/sync-dirs.yamlAdds a new step "Heal libs.versions.toml" between the existing "Sync directories and files" and "Clean up temporary branch" steps. The step:
libs.<X>reference inside the freshly-syncedbuild-logic/libs.<a>→[libraries]key<a>libs.plugins.<a>→[plugins]key<a>libs.versions.<a>→[versions]key<a>libs.findLibrary(\"…\"), etc.) and the catalog filenamelibs.versions.tomlgradle/libs.versions.toml(accessed via$TEMP_BRANCH) and inserts it into the appropriate section of the consumer's catalogversion.refsan undeclared version key, that version key is also pulled and inserted into[versions]before the aliassync-dirs.shAdds the same heal logic as a
heal_libs_versions_toml()function invoked between the sync loop and the temp-branch cleanup. Local CLI use of sync-dirs (developer runningbash sync-dirs.shmanually) gets the same self-healing.The follow-up
git addis widened to includegradle/libs.versions.tomlso the heal's additions ride into the commit.Verified end-to-end
Tested against field-officer's pre-fix state (catalog reverted to commit
408d9cba— the broken state from ~24h ago, before manual catch-up):Net diff
.github/workflows/sync-dirs.yamlsync-dirs.shNo new files. Workflow becomes self-healing — discovery + remediation in one atomic sync PR.
Test plan
[libraries]/[plugins]/[versions]findLibrary/findVersion/findBundle/findPlugin)libs.versions.tomlcatalog filename string-literalSummary by CodeRabbit