Two new gates that would have prevented today's
post-#87 template-extraction bug parade:
1. **Bare-import lint** — fail-fast pre-build check that grep's
template *.py files for `from <runtime_module> import` (where
<runtime_module> is in the closed list mirroring workspace/*.py
basenames). When the runtime was bundled into workspace/, bare
imports resolved against sibling files; in standalone template
repos they explode at startup. Five separate templates shipped
broken on 2026-04-27 because of this exact pattern (claude-code:
plugins, executor_helpers, heartbeat, a2a_client, platform_auth;
langgraph: agent, a2a_executor; deepagents: a2a_executor;
gemini-cli: config, executor_helpers x2). The lint runs before
docker login + buildx setup so a bad PR returns red in seconds.
2. **Import every /app/*.py at boot** (deeper smoke) — replaces
`python -c "import adapter"` with a loop importing every Python
module at /app/. The old single-import didn't traverse to
sibling modules adapter.py imports lazily inside
`create_executor()` (the executor.py family). That's why the
hermes a2a-sdk migration bug and langgraph's bare a2a_executor
import slipped through every prior gate even though the boot
smoke "passed." Importing every module module-level forces all
imports to resolve, including those in executor.py.
Both gates use the closed-list pattern (deliberate, easy to update,
no false-positives on legit third-party imports). The runtime module
list mirrors the equivalent in scripts/build_runtime_package.py;
both should be updated together when a new top-level workspace
module ships.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>