Break manifest import cycle: extract ManifestBottle to a leaf module #314

Merged
didericis merged 1 commits from manifest-break-import-cycle into main 2026-06-26 23:53:59 -04:00
Collaborator

Summary

Removes the one legitimate architecture snag from the recent quality eval: a circular dependency in the manifest layer.

manifest.py imported the extends:/loader resolvers (merge_bottles_runtime, resolve_bottles, load_bottle_chain_from_dir, …), while those resolvers needed ManifestBottle back from manifest.py — a true bidirectional manifest ↔ manifest_extends/manifest_loader cycle, papered over with in-function imports and TYPE_CHECKING guards. That's the opposite of clear dependency inversion.

Change

  • New leaf module manifest_bottle.py holds ManifestBottle. It depends only on the existing leaf modules (manifest_util, manifest_agent, manifest_egress, manifest_git, manifest_schema), so it sits at the bottom of the manifest layer.
  • manifest.py re-exports ManifestBottle (still in __all__), so every from .manifest import ManifestBottle caller — git_gate*.py, egress.py, tests — is unaffected. Public API unchanged.
  • manifest_extends / manifest_loader now import ManifestBottle from the leaf and their other deps from the real source modules, all at module top; TYPE_CHECKING blocks removed.
  • manifest.py imports the extends/loader/schema/yaml_subset/log helpers at module top; all per-function lazy imports in the cluster are gone.

Resulting graph is a clean DAG: manifest → {manifest_extends, manifest_loader, manifest_bottle, …}, manifest_loader → manifest_extends → manifest_bottle → leaves.

Verification

  • Full unit suite green (1486 tests).
  • pyright clean on all four modules.
  • Import smoke test confirms no cycle and that manifest.ManifestBottle resolves to manifest_bottle.
  • No behavior change — pure structural refactor.
## Summary Removes the one legitimate architecture snag from the recent quality eval: a circular dependency in the manifest layer. `manifest.py` imported the `extends:`/loader resolvers (`merge_bottles_runtime`, `resolve_bottles`, `load_bottle_chain_from_dir`, …), while those resolvers needed `ManifestBottle` back from `manifest.py` — a true bidirectional `manifest ↔ manifest_extends`/`manifest_loader` cycle, papered over with in-function imports and `TYPE_CHECKING` guards. That's the opposite of clear dependency inversion. ## Change - New leaf module **`manifest_bottle.py`** holds `ManifestBottle`. It depends only on the existing leaf modules (`manifest_util`, `manifest_agent`, `manifest_egress`, `manifest_git`, `manifest_schema`), so it sits at the bottom of the manifest layer. - `manifest.py` **re-exports** `ManifestBottle` (still in `__all__`), so every `from .manifest import ManifestBottle` caller — `git_gate*.py`, `egress.py`, tests — is unaffected. Public API unchanged. - `manifest_extends` / `manifest_loader` now import `ManifestBottle` from the leaf and their other deps from the real source modules, all at module top; `TYPE_CHECKING` blocks removed. - `manifest.py` imports the extends/loader/schema/yaml_subset/log helpers at module top; **all per-function lazy imports in the cluster are gone**. Resulting graph is a clean DAG: `manifest → {manifest_extends, manifest_loader, manifest_bottle, …}`, `manifest_loader → manifest_extends → manifest_bottle → leaves`. ## Verification - Full unit suite green (1486 tests). - pyright clean on all four modules. - Import smoke test confirms no cycle and that `manifest.ManifestBottle` resolves to `manifest_bottle`. - No behavior change — pure structural refactor.
didericis-claude added 1 commit 2026-06-26 23:42:28 -04:00
refactor(manifest): break import cycle by extracting ManifestBottle to a leaf module
test / unit (pull_request) Successful in 57s
test / integration (pull_request) Successful in 27s
test / coverage (pull_request) Successful in 1m23s
lint / lint (push) Successful in 2m24s
test / unit (push) Successful in 59s
test / integration (push) Successful in 26s
test / coverage (push) Successful in 1m17s
Update Quality Badges / update-badges (push) Successful in 1m13s
f787764364
manifest.py imported the extends/loader resolvers, while those resolvers
needed ManifestBottle back from manifest.py — a true bidirectional cycle
papered over with in-function imports and TYPE_CHECKING guards (not clear
dependency inversion).

Extract ManifestBottle into a new leaf module manifest_bottle.py that depends
only on the other leaf modules (manifest_util/agent/egress/git/schema).
manifest.py re-exports ManifestBottle, so `from .manifest import ManifestBottle`
callers are unaffected. With the cycle gone:

- manifest_extends and manifest_loader import ManifestBottle from
  manifest_bottle and their other deps from the real source modules, all at
  top level (TYPE_CHECKING block removed).
- manifest.py imports the extends/loader/schema/yaml_subset/log helpers at
  module top; all per-function lazy imports in the cluster are removed.

No behavior change; full unit suite green, pyright clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NkwFXLFff9PYPy4wgVBJp9
didericis merged commit f787764364 into main 2026-06-26 23:53:59 -04:00
Sign in to join this conversation.