Skip to content

Decompose inputs/n8n.py into inputs/n8n/ package (v0.21 E8)#118

Open
pengfei-threemoonslab wants to merge 2 commits into
mainfrom
claude/practical-wescoff-08ce34
Open

Decompose inputs/n8n.py into inputs/n8n/ package (v0.21 E8)#118
pengfei-threemoonslab wants to merge 2 commits into
mainfrom
claude/practical-wescoff-08ce34

Conversation

@pengfei-threemoonslab
Copy link
Copy Markdown
Contributor

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

Module LOC Concern
__init__.py 54 Re-export public surface (N8nAdapter, load_n8n_artifacts)
_common.py 300 Constants, _NodeItem/_Edge, leaf helpers, node-kind classification
_secrets.py 122 Secret scanning against v0.19 global SECRET_PATTERNS
_auth_risk.py 148 Credentials, AuthInfo, risk-hint heuristics, HTTP path hint
_tools.py 492 Tool extraction for 5 flavours + projected mcp + schemas + MCP selection
_workflows.py 464 File loading, shape detection, _extract_workflow, edges, record builders
_adapter.py 249 N8nAdapter, load_n8n_artifacts, auxiliary loaders

Largest sub-module is _tools.py at 492 lines — still under the typical largest-adapter threshold, and far below the original 1493.

Dependency direction (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 "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

from agents_shipgate.inputs.n8n import N8nAdapter        # used by inputs/protocol.py
from agents_shipgate.inputs.n8n import load_n8n_artifacts # used by tests/test_n8n.py

Both external import sites keep working unchanged via __init__.py re-exports. 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.

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 the rglob fix.
  • python scripts/generate_schemas.py --check: clean.
  • ruff check .: clean.
  • Full pytest suite: pass; coverage 88.13% (above 75% floor).

File-size delta

  • Old: 1493 LOC monolithic
  • New: 1829 LOC across 7 files
  • 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.

Stability

  • No report_schema_version bump (catalog-internal change).
  • No public Python API change.
  • No CLI flag change.
  • STABILITY.md unchanged.

🤖 Generated with Claude Code

pengfei-threemoonslab and others added 2 commits May 22, 2026 23:05
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant