From 0335ec169c90474e36bf1d857d2ec0d1401619c8 Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Mon, 27 Apr 2026 06:19:23 -0700 Subject: [PATCH] feat(lint): read runtime module list from wheel manifest, not inline (#9) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switches the bare-imports lint from an inline RUNTIME_MODULES list to the _runtime_modules.json manifest emitted by molecule-core's build_runtime_package.py. Eliminates the third place the runtime module list lived — now the build script is the single source of truth. Tonight surfaced that the same closed list lived in three places that drifted independently. The build script's TOP_LEVEL_MODULES went stale on transcript_auth, the smoke-test step here had a hardcoded mirror that would have drifted next time a top-level module was added, and runtime-pin-compat tested transitively via import molecule_runtime.main (which only catches breakage, not drift). One source of truth fixes all three at once. Implementation: - pip download molecule-ai-workspace-runtime --no-deps to /tmp - unzip _runtime_modules.json from the wheel - merge top_level_modules + subpackages into the regex alternation (subpackages can be bare-imported too — `from lib.pre_stop`) - on any fetch failure (network, missing manifest in older wheel), fall back to the inline list with a workflow warning so the lint still runs but the operator knows to investigate Two consequences: - Templates rebuilt against runtime ≥ the version that ships the manifest get the always-fresh list automatically. - Templates rebuilt against the old wheel (pre-manifest) still get the working inline list — no regression. Future cleanup (separate PR after a few release cycles): once all template repos have rebuilt at least once with the manifest path, the inline fallback can shrink to a panic message. Co-authored-by: Claude Opus 4.7 (1M context) --- .github/workflows/publish-template-image.yml | 43 ++++++++++++++++---- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/.github/workflows/publish-template-image.yml b/.github/workflows/publish-template-image.yml index 1a80269..20a9590 100644 --- a/.github/workflows/publish-template-image.yml +++ b/.github/workflows/publish-template-image.yml @@ -99,16 +99,45 @@ jobs: # package. This bit claude-code (5 imports), langgraph, # deepagents, and gemini-cli on 2026-04-27 — each one a # separate workspace-stuck-in-provisioning incident. - # The set of names mirrors workspace/*.py basenames at the time - # this lint was added; if a new runtime module ships, the - # build will fail loud with a clear message instead of - # silently shipping broken templates. Fail-fast: this runs - # before docker login + buildx setup so a bad PR returns red - # in seconds, not minutes. + # + # Source of truth: molecule_runtime/_runtime_modules.json + # inside the published wheel (emitted by + # scripts/build_runtime_package.py). Pulling the manifest + # from PyPI's latest wheel ensures the lint never drifts from + # the rewriter's actual closed list. If the manifest can't be + # fetched (older wheel, PyPI down, etc.), falls back to the + # inline list — known to be correct as of 2026-04-27 — so + # the lint never silently passes on a fetch failure. + # + # Fail-fast: this runs before docker login + buildx setup so + # a bad PR returns red in seconds, not minutes. shell: bash run: | set -eu - RUNTIME_MODULES='plugins|adapter_base|config|main|preflight|prompt|coordinator|consolidation|events|heartbeat|transcript_auth|runtime_wedge|watcher|skill_loader|policies|adapters|builtin_tools|executor_helpers|a2a_executor|a2a_client|a2a_tools|a2a_cli|a2a_mcp_server|agent|agents_md|initial_prompt|molecule_ai_status|platform_auth|shared_runtime' + + # Fallback list — used only when the manifest fetch fails. + # Mirrors scripts/build_runtime_package.py:TOP_LEVEL_MODULES + # at the time this comment was written. + FALLBACK_MODULES='plugins|adapter_base|config|main|preflight|prompt|coordinator|consolidation|events|heartbeat|transcript_auth|runtime_wedge|watcher|skill_loader|policies|adapters|builtin_tools|executor_helpers|a2a_executor|a2a_client|a2a_tools|a2a_cli|a2a_mcp_server|agent|agents_md|initial_prompt|molecule_ai_status|platform_auth|shared_runtime' + + RUNTIME_MODULES="" + mkdir -p /tmp/runtime-wheel + if pip download --quiet molecule-ai-workspace-runtime --no-deps -d /tmp/runtime-wheel 2>/dev/null; then + WHEEL=$(ls /tmp/runtime-wheel/*.whl 2>/dev/null | head -1) + if [ -n "$WHEEL" ]; then + # Pull both top_level + subpackage names; both can be bare-imported. + RUNTIME_MODULES=$(unzip -p "$WHEEL" molecule_runtime/_runtime_modules.json 2>/dev/null \ + | python3 -c "import sys,json; m=json.load(sys.stdin); print('|'.join(sorted(set(m['top_level_modules']) | set(m['subpackages']))))" 2>/dev/null || echo "") + fi + fi + + if [ -n "$RUNTIME_MODULES" ]; then + echo "::notice::lint module list pulled from molecule-ai-workspace-runtime wheel manifest" + else + RUNTIME_MODULES="$FALLBACK_MODULES" + echo "::warning::could not read _runtime_modules.json from PyPI wheel — using inline fallback list" + fi + # Match `from import` at start of line OR after any whitespace # (function-scope imports inside if/try blocks count too). if HITS=$(grep -nE "^\s*from (${RUNTIME_MODULES}) import" *.py 2>/dev/null); then