Decompose inputs/n8n.py into inputs/n8n/ package (v0.21 E8)#118
Open
pengfei-threemoonslab wants to merge 2 commits into
Open
Decompose inputs/n8n.py into inputs/n8n/ package (v0.21 E8)#118pengfei-threemoonslab wants to merge 2 commits into
pengfei-threemoonslab wants to merge 2 commits into
Conversation
Closes round-3 architecture-review evolution item E8. The largest
input adapter was 1493 lines monolithic — out of line with the typical
adapter file (mcp.py 148, openapi.py 343, langchain.py 305) and
flagged as a structural-debt item for four consecutive review rounds.
New layout under src/agents_shipgate/inputs/n8n/ (6 sub-modules,
underscore-prefixed for "internal to the package"):
- __init__.py — re-exports N8nAdapter + load_n8n_artifacts
- _common.py — constants, _NodeItem, _Edge, leaf helpers, node
kind classification
- _secrets.py — secret scanning of params/notes/pinData/staticData
- _auth_risk.py — auth/credentials/risk-hint synthesis
- _tools.py — Tool extraction (ai/workflow/code/http/mcp_client)
- _workflows.py — file loading, shape detection, _extract_workflow,
connection edges, node-record builders
- _adapter.py — N8nAdapter, load_n8n_artifacts, auxiliary loaders
Dependency direction is a DAG at module-load:
_common ← _secrets, _auth_risk ← _tools ← _workflows ← _adapter
_tools calls back into _workflows (for record builders and
dynamic-surface emission) via late imports inside the call sites
that need them. This is the inherent shape of the n8n pipeline:
workflows own the "what is a node" domain, tools own "what is a Tool
emitted from a node." Keeping the late imports localized means the
static import graph remains one-way.
Public surface preserved:
- from agents_shipgate.inputs.n8n import N8nAdapter
- from agents_shipgate.inputs.n8n import load_n8n_artifacts
Both external import sites (inputs/protocol.py:282 for adapter
registration; tests/test_n8n.py:20) keep working unchanged. The
public symbols are re-exported by __init__.py with no behavior change.
One adjacent contract-test fix (tests/test_public_surface_contract.py):
test_supported_inputs_match_adapter_class_vars_bidirectionally walked
adapter_dir.glob("*.py") looking for the source_type ClassVar literal.
That pattern was written when n8n was a single file; with n8n now a
sub-package the literal lives at inputs/n8n/_adapter.py. Switched to
rglob("*.py") with a __pycache__ guard. Same coverage, sub-package
support included.
Verification:
- tests/test_n8n.py: 30/30 pass byte-identical.
- tests/test_adapter_static_only.py: 216/216 pass — the new
sub-modules are scanned by the v0.18 trust-lint and clean.
- tests/test_fixture_no_import.py::test_n8n_adapter_does_not_import_sibling_python: pass.
- tests/test_adapter_entry_point_discovery.py: 36/36 pass —
N8nAdapter remains discoverable through the registry.
- tests/test_public_surface_contract.py: full pass after the
rglob fix.
- python scripts/generate_schemas.py --check: clean.
- ruff: clean.
- Full pytest: pass; coverage 88.13% (above 75% floor).
File-size delta:
- Old: 1493 LOC monolithic
- New: 1829 LOC across 7 files (54 + 249 + 148 + 300 + 122 + 492 + 464)
- Largest sub-module: _tools.py at 492 LOC (still under the typical
largest-adapter threshold).
- Net +336 LOC from per-module docstrings, import groupings, and the
re-export header — the inherent cost of decomposition. The
cognitive-load reduction is in per-file size, not aggregate.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…onflict # Conflicts: # CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes round-3 architecture-review evolution item E8. The largest input adapter was 1493 lines monolithic — out of line with the typical adapter file (mcp.py 148, openapi.py 343, langchain.py 305) and flagged as a structural-debt item for four consecutive review rounds.
The file is now a 6-module package under
src/agents_shipgate/inputs/n8n/with per-concern boundaries.New layout
__init__.pyN8nAdapter,load_n8n_artifacts)_common.py_NodeItem/_Edge, leaf helpers, node-kind classification_secrets.pySECRET_PATTERNS_auth_risk.pyAuthInfo, risk-hint heuristics, HTTP path hint_tools.pymcp+ schemas + MCP selection_workflows.py_extract_workflow, edges, record builders_adapter.pyN8nAdapter,load_n8n_artifacts, auxiliary loadersLargest sub-module is
_tools.pyat 492 lines — still under the typical largest-adapter threshold, and far below the original 1493.Dependency direction (DAG at module-load)
_toolscalls back into_workflows(for record builders and dynamic-surface emission) via late imports inside the call sites that need them. This is the inherent shape of the n8n pipeline — workflows own "what is a node," tools own "what is a Tool emitted from a node" — and the late-import scoping keeps the static import graph one-way.Public surface preserved
Both external import sites keep working unchanged via
__init__.pyre-exports. No behavior change.One adjacent contract-test fix
tests/test_public_surface_contract.py::test_supported_inputs_match_adapter_class_vars_bidirectionallywalkedadapter_dir.glob(\"*.py\")looking for thesource_typeClassVar literal. That pattern was written when n8n was a single file; with n8n now a sub-package the literal lives atinputs/n8n/_adapter.py. Switched torglob(\"*.py\")with a__pycache__guard. Same coverage, sub-package support included.Test plan
tests/test_n8n.py: 30/30 pass byte-identical (no behavior change).tests/test_adapter_static_only.py: 216/216 pass — new sub-modules scanned by v0.18 trust-lint and clean.tests/test_fixture_no_import.py::test_n8n_adapter_does_not_import_sibling_python: pass.tests/test_adapter_entry_point_discovery.py: 36/36 pass — N8nAdapter remains discoverable through the registry.tests/test_public_surface_contract.py: full pass after therglobfix.python scripts/generate_schemas.py --check: clean.ruff check .: clean.pytestsuite: pass; coverage 88.13% (above 75% floor).File-size delta
Stability
report_schema_versionbump (catalog-internal change).🤖 Generated with Claude Code