refactor(server): decompose 7 oversized server files into directory packages (#4663) - #4679
Merged
Trecek merged 42 commits intoAug 19, 2026
Merged
Conversation
Trecek
force-pushed
the
impl-issue-4663-decompose-server-tools-20260817-124812
branch
from
August 18, 2026 05:02
e7cbc2c to
38b1725
Compare
Adds 7 sibling-set tests in tests/arch/test_subpackage_isolation.py that pin the exact set of submodules each decomposed package must contain after the 7-file decomposition described in issue #4663 lands. These tests intentionally fail until each decomposition step creates the expected directory package.
Splits the 1301-line server._response_budget module into a directory package with 4 sibling submodules + __init__.py facade: - _primitives.py (~122 lines) - RESPONSE_* constants, primitive helpers - _projection.py (~670 lines) - projection + spill-for-delivery-bound - _spill.py (~164 lines) - bounded failure + spill envelope machinery - _enforce.py - enforce_response_budget + checkpoint-segmented shaping - __init__.py (~32 lines) - re-exports all 11 symbols from original __all__ The __init__.py facade preserves the flat import surface so consumer imports keep working without modification. Cross-submodule dependencies are routed through explicit imports; no circular-import risk. This is part of issue #4663's decomposition to bring all source files under the 750-line per-file ceiling enforced by test_no_src_module_exceeds_line_limit().
Splits the 1043-line server.tools._execution_helpers module into a directory package with 4 sibling submodules + __init__.py facade: - _skill_contract.py - run-skill contract lifecycle and serialization - _dispatch_metadata.py - dispatch metadata, projection context, AuditOutputMode, audit output contract selection - _run_cmd_spill.py - run_cmd spill lifecycle, stream processing, propagate_session_deadline - _run_python_coercion.py - run_python path-arg anchoring and call coercion helpers - __init__.py - re-exports the 35+ public symbols consumers import The __init__.py facade preserves the flat import surface so consumer imports keep working without modification. Note: invocation_member_names lives in _dispatch_metadata (per its actual source location) and is re-exported from there, not from _skill_contract as the initial plan implied. This is part of issue #4663's decomposition to bring all source files under the 750-line per-file ceiling enforced by test_no_src_module_exceeds_line_limit().
Splits the 955-line server.tools._evidence_reader module into a directory package with 4 sibling submodules + __init__.py facade: - _authority.py (374 lines) - capability hashing, secure authority I/O, authority lifecycle. Imports EvidenceReaderError / EvidenceReaderLimits via late (function-level) imports from _startup.py to avoid a circular import. - _invocation.py - invocation lifecycle, receipts, call locks. References dataclasses via late imports from _startup.py. - _reader.py - page reading helpers and scope digest. - _startup.py - owns the 5 top-level exception/dataclass declarations (EvidenceReaderError, EvidenceReaderLimits, EvidenceReaderInvocation, EvidenceReaderPage, EvidenceReaderReceipt) plus the startup validation, receipt loading, and revocation helpers. - __init__.py - re-exports all public symbols consumers import. The circular-import topology inherent in the plan's split (where _authority needs the dataclasses to raise authority-tampered errors and _startup needs authority helpers to validate at startup) is broken by moving sibling-submodule imports inside the function body of the consumer, marked with `# circular-break` comments to match the codebase convention. This is part of issue #4663's decomposition to bring all source files under the 750-line per-file ceiling enforced by test_no_src_module_exceeds_line_limit().
After Steps 1.1 and 1.2 decomposed server/_response_budget.py and server/tools/_execution_helpers.py into multi-file packages, the following exemption entries in tests/arch/test_subpackage_isolation.py reference paths that no longer exist: - "_response_budget.py" (rationale REQ-CNST-010-E12, 1500 lines) - "server/tools/_execution_helpers.py" (rationale REQ-CNST-010-E20, 1075 lines) The new sibling files (e.g. server/_response_budget/_primitives.py) all fall through to the 1000-line default of test_no_src_module_exceeds_line_limit() since each is well under 750 lines. Deleting these entries keeps the exemption table as the single-source-of-truth for files that genuinely need waivers.
Splits the 882-line server.tools.tools_fleet_dispatch module into a directory package with 3 sibling submodules + __init__.py facade: - _provenance.py - dispatch provenance attach/bind helpers - _campaign_state.py - campaign state write/confirm/identity helpers - _handlers.py - dispatch_food_truck + record_gate_dispatch handlers - __init__.py - re-exports both @mcp.tool() entry points and imports all sibling submodules for side-effect registration This is part of issue #4663's decomposition to bring all source files under the 750-line per-file ceiling enforced by test_no_src_module_exceeds_line_limit().
…kage Splits the 786-line server.tools.tools_pipeline_tracker module into a directory package with 3 sibling submodules + __init__.py facade: - _authority.py - tracker authority retention/release/dependency helpers - _status.py - tracker status builder + count helpers - _handlers.py - 3 @mcp.tool() handlers (record_pipeline_step, recover_run_skill_result, complete_run_skill_result) plus mark_step_complete (used by tools_execution.py) - __init__.py - re-exports the public API and imports all sibling submodules for side-effect registration This is part of issue #4663's decomposition to bring all source files under the 750-line per-file ceiling enforced by test_no_src_module_exceeds_line_limit().
Splits the 2147-line server.tools.tools_kitchen module into a directory package with 10 sibling submodules + __init__.py facade: - _open_kitchen.py - open_kitchen handler + _open_kitchen_handler - _open_kitchen_transition.py - kitchen transition lifecycle helpers plus the _OPEN_KITCHEN_REQUEST_CTX ContextVar - _open_kitchen_errors.py - _kitchen_failure_envelope + recipe validation - _close_kitchen.py - close_kitchen handler + close_kitchen_handler - _lock_ingredients.py - lock_ingredients handler + helpers - _reload_session.py - reload_session handler + helpers - _disable_quota_guard.py - disable_quota_guard handler - _get_recipe.py - get_recipe @mcp.resource + helpers - _hook_config.py - hook-config payload builders + writers - _tracker_authority.py - pipeline tracker authority helpers Circular-import topology inherent in the plan (open_kitchen ↔ get_recipe via _attach_transition_fields / _render_ingredients_only_response) is broken by routing _attach_transition_fields through the neutral _open_kitchen_transition module. The __init__.py facade re-exports all 6 @mcp.tool()/@mcp.resource() entry points plus the 12 internal helpers that ~144 test monkey-patch sites rebind. Per registry-tracer finding 5, internal helper imports between submodules route through the facade so test patches continue to rebind the right attribute. This is part of issue #4663's decomposition to bring all source files under the 750-line per-file ceiling enforced by test_no_src_module_exceeds_line_limit().
Splits the 869-line server._lifespan module into a directory package with 3 sibling submodules + __init__.py facade: - _startup_checks.py (~250 lines) - one-shot synchronous startup checks plus _retain_context_tracker_authority (one of the 6 circular-break re-point sites) - _session_boots.py (~400 lines) - 5 per-session-type auto-gate boots plus _pre_reveal_kitchen, _cleanup_stale_loop, and _LIFESPAN_BOOT_REGISTRY - _lifespan.py (~250 lines) - FastMCP lifespan glue (_autoskillit_lifespan via @asynccontextmanager, plus deferred-init and backend-MCP-registration handlers) - __init__.py (~80 lines) - re-exports the public API The 6 circular-break imports from tools_kitchen inside the original _lifespan.py are re-pointed with this exact mapping: - lines 92, 855: _retain_kitchen_tracker_authority and _release_kitchen_tracker_authority → tools_kitchen._tracker_authority - lines 406, 477, 536, 688: _write_hook_config → tools_kitchen._hook_config This is part of issue #4663's decomposition to bring all source files under the 750-line per-file ceiling enforced by test_no_src_module_exceeds_line_limit().
After Step 2.1 decomposed server/tools/tools_kitchen.py into a 12-file package (well under 1000 lines per file), the following exemption entry in tests/arch/test_subpackage_isolation.py references a path that no longer exists: - "tools_kitchen.py" (rationale REQ-CNST-010-E7, 2260 lines) The new sibling files (server/tools/tools_kitchen/__init__.py and 10 submodules) all fall through to the 1000-line default of test_no_src_module_exceeds_line_limit() since each is well under 750 lines. Deleting this entry keeps the exemption table as the single-source-of-truth for files that genuinely need waivers.
After Steps 2.1 and 3.1 decompose server/tools/tools_kitchen.py (→12 files) and server/_lifespan.py (→4 files), several arch/server tests that read the soon-to-be-deleted flat files via hardcoded Path(...) raise FileNotFoundError. Each test now reads the new package's __init__.py plus iterates all sibling submodules via rglob: - tests/arch/test_boot_step_symmetry.py: KITCHEN_PATH and LIFESPAN_PATH - tests/arch/test_kitchen_id_assignment.py: KITCHEN_FILES tuple - tests/arch/test_startup_budget.py: lifespan AST scan - tests/arch/test_exploration_request_identity_ownership.py: lifespan AST scan - tests/server/test_lifespan_readiness_structural.py: setup_method lifespan AST scan - tests/arch/test_durable_artifact_writers_guard.py: _SCOPED_MODULES now lists the 4 new _lifespan/ siblings Plus: - tests/arch/test_subpackage_isolation.py:174 (_SINGLETON_SAFE_ASSIGNMENTS) updated to point at the new _open_kitchen_transition submodule where _OPEN_KITCHEN_REQUEST_CTX lives - tests/arch/test_import_paths.py:248 (REQ-IMP-006) and tests/arch/test_import_paths.py:350 (REQ-IMP-007 allowlist) now use rglob over the new tools_kitchen package
…le imports
The original Step 2.1 decomposition imported cross-submodule helpers
through the autoskillit.server.tools.tools_kitchen facade for
monkey-patch reach. This created a hard circular dependency:
- __init__.py imports _open_kitchen
- _open_kitchen.py imports back from the facade
The facade re-exports were deliberately preserved so existing
``patch("autoskillit.server.tools.tools_kitchen._X")`` test sites
continue rebinding attributes on the package facade. To break the
load-order cycle, this change routes the _open_kitchen.py imports to
the direct submodule paths (_open_kitchen_transition, _tracker_authority,
_open_kitchen_errors, _get_recipe, _hook_config) so __init__.py can be
fully populated before _open_kitchen.py begins executing.
The facade stays the single source of truth for external consumers
and test-side monkey-patches; this change only restructures _open_kitchen.py's
internal imports to escape the load-cycle.
Six legacy flat modules survived the decomposition of issue #4663 alongside their package replacements: - src/autoskillit/server/_lifespan.py (869 lines) - src/autoskillit/server/tools/tools_kitchen.py (2147 lines) - src/autoskillit/server/tools/_execution_helpers.py (1043 lines) - src/autoskillit/server/tools/_evidence_reader.py (955 lines) - src/autoskillit/server/tools/tools_fleet_dispatch.py (882 lines) - src/autoskillit/server/tools/tools_pipeline_tracker.py (786 lines) Python's FileFinder resolves the directory package before the same-named module, so the legacy files became unreachable dead copies of the live authority. The decomposition's line-ceiling objective was asserted only against the new siblings, leaving the surviving legacy tools_kitchen.py (2147 lines) and _execution_helpers.py (1043 lines) over the 1000-line limit with no exemption backing them. This change retires the six legacy flat modules and expands each package facade to re-export every symbol the legacy module exposed, preserving backward compatibility for the 358 imports of these paths. Test files that referenced the legacy module paths are migrated to their package submodule equivalents: - tests/server/test_no_path_cwd_in_tools.py - tests/infra/test_schema_version_convention.py - tests/infra/test_plugin_source_ratchets.py - tests/arch/test_python_no_hardcoded_temp.py - tests/test_test_filter_cascade.py - tests/fleet/test_state_lock_contract.py - tests/arch/test_subpackage_isolation.py Also fixes the indentation bug in tests/arch/test_kitchen_id_assignment.py:37 where the AST walk was running at the wrong indentation level — Python lacks block scoping, so 'tree' was retaining the last per-package file's parse and the post-refactor test was silently scanning only one file per package. Address the review warnings/infos in the same commit where simple: - Remove dead 'mcp' import in tools_kitchen/_open_kitchen_transition.py - Remove empty except OSError: pass in _run_cmd_spill.py and _close_kitchen.py (unlink(missing_ok=True) is already idempotent) - Fix 'Backward-compat' slop label in tools_fleet_dispatch/_handlers.py - Remove empty TYPE_CHECKING: pass block in _lifespan/_startup_checks.py - Remove dead side-effect imports in _lifespan/__init__.py facade - Fix 'except BaseException' too-broad catch in _evidence_reader/_startup.py - Add module docstrings to _response_budget/_spill.py and _enforce.py - Update stale docstring in tools_kitchen/_open_kitchen.py to reflect the post-circular-import-fix import strategy (direct submodule imports, not facade-mediated) Co-Authored-By: Claude <noreply@anthropic.com>
The 6 legacy flat module deletions broke several arch guards that
scanned src/autoskillit/server/tools/glob("*.py") only:
- test_layer_enforcement.py::test_all_mcp_tools_are_registered and
test_tool_subset_tags_match_decorators now also walk into the
tools_kitchen, tools_fleet_dispatch, and tools_pipeline_tracker
packages so the @mcp.tool decorators living in their submodules
are visible to the bidirectional registry check.
- test_transforms_hygiene.py::test_fleet_tools_carry_required_subset_tag
and the two adjacent tag-partition tests gain an
_iter_tool_modules() helper that walks the same packages.
- test_subpackage_isolation.py::SINGLETON_ALLOWED_MODULES gains
_primitives (SHA-256 digests derived once at import) and
_provenance (ContextVars) entries for the new submodules.
- test_durable_artifact_writers_guard.py::_NON_HOOK_ALLOWLIST adds
run_startup_drift_check — the legacy file's atomic_write call
moved to server/_lifespan/_startup_checks.py with the decomposition
but the exemption did not follow.
- _open_kitchen.py hoists the deferred _collect_disabled_feature_tags
import to module level (lower layers cannot cause circular imports
with server/) and tags every other deferred autoskillit import with
the required # circular-break comment.
Co-Authored-By: Claude <noreply@anthropic.com>
…patches Tests throughout the suite use ``mock.patch(\"autoskillit.server.X.Y\")`` to swap dependencies. The legacy flat module exposed every imported symbol as a module attribute; the decomposed package facade only re-exports symbols that are part of the canonical public surface. This commit restores the patch reachability by re-exporting the additional symbols the legacy module happened to expose via its module namespace: - ``autoskillit.server._lifespan`` facade re-exports ``_core_paths``, ``iter_all_scope_paths``, ``discover_campaign_state_files``, ``_collect_disabled_feature_tags``, ``register_active_kitchen``, ``resolve_kitchen_id``, ``reap_stale_dispatches_async``, ``create_background_task``, and ``_get_ctx_or_none``. - ``autoskillit.server.tools.tools_kitchen`` facade re-exports ``create_background_task``, ``find_latest_session_id``, ``clear_recipe_execution``, ``_collect_disabled_feature_tags``, ``discover_campaign_state_files``, ``execute_dispatch``, ``iter_all_scope_paths``, ``prepare_recipe_segment_delivery``, and a late-bound ``mcp`` (via module ``__getattr__`` to avoid circular import through ``autoskillit.server``). - ``autoskillit.server.tools.tools_pipeline_tracker`` facade re-exports ``prepare_recipe_segment_delivery``. Co-Authored-By: Claude <noreply@anthropic.com>
… lifecycle The decomposition of tools_kitchen.py into a package spread its lifecycle operations across sibling submodules. _tracker_authority.py imports the lifecycle symbols (ArtifactLease, RetiringArtifactRecord, etc.) but _close_kitchen.py — which still performs lifecycle writes like hook_cfg_path.unlink() — does not. The scanner's per-file lifecycle check needs to recognize the package collectively. Co-Authored-By: Claude <noreply@anthropic.com>
Adversarial validation of my earlier slop cleanup work caught three
load-bearing exception suppressors the reviewer had incorrectly flagged
as dead code:
1. tools_kitchen/_close_kitchen.py: restore try/except around
hook_cfg_path.unlink and overlay_path.unlink. The handler is a
best-effort teardown that runs after ctx.gate.disable(); an escaping
OSError would otherwise abort the subsequent _session_config_overrides
reset, baseline_config restoration, and tracker-authority release —
leaving the kitchen half-closed. test_close_kitchen_continues_when_
hook_config_removal_fails is the regression tripwire.
2. _execution_helpers/_run_cmd_spill.py:154: restore try/except around
orphan stream_path.unlink inside the CaptureReadError handler. The
suppression was intentional — the path is already in the
capture_error envelope and OSError on the orphan cleanup must not
replace a graceful "capture_failed:" with an uncaught exception.
3. _evidence_reader/_startup.py:188: revert BaseException -> Exception.
The handler's job is lock/fd hygiene before re-raising; narrowing to
Exception bypasses both branches on KeyboardInterrupt/SystemExit,
leaving a stale call lock in a surviving invocation_dir — exactly
the state _acquire_call_lock guards against. The BaseException form
is the established pattern across lock/fd release sites in the
repo.
Also add _ACTIVE_DISPATCH_PROVENANCE and _BOUND_DISPATCH_PROVENANCE
ContextVars to the tools_fleet_dispatch facade — adversarial validation
flagged them as missing despite being patched by tests via
mock.patch("autoskillit.server.tools.tools_fleet_dispatch.X").
Co-Authored-By: Claude <noreply@anthropic.com>
…_disabled_feature_tags Hoisting the import to module level (commit 5e541ce) introduced an infinite-recursion bug: the imported \`autoskillit.core._collect_disabled_feature_tags\` was rebound to the same name that an existing local wrapper function used, so every call into the wrapper resolved to the wrapper itself and recursed until RecursionError. The wrapper was a thin default-value-providing shim — the only call site (line 272 of _redisable_subsets) already substitutes the default \`features or {}\` before delegating, so the wrapper contributed nothing the caller wasn't already doing. Drop it; the imported core function is the single authority. test_tools_kitchen_visibility.py mocks \`tools_kitchen._collect_disabled_feature_tags\` and so masked the recursion; that test now continues to pass against the imported function directly. Co-Authored-By: Claude <noreply@anthropic.com>
Adversarial validation of my test-file updates caught three additional references to the deleted flat modules that I had missed: 1. tests/arch/test_codex_env_forward_bridge.py: _LONG_LIVED_CAPTURE_AUTHORITY_FILES referenced the deleted tools_fleet_dispatch.py flat module. Pointed at the new tools_fleet_dispatch/_handlers.py submodule where the same long-lived capture authority actually lives. 2. tests/server/conftest.py::_patch_kitchen_reaper (autouse): monkeypatches autoskillit.server.tools.tools_kitchen.reap_stale_dispatches_async but the symbol was not re-exported on the facade. Added reap_stale_dispatches_async to tools_kitchen/__init__.py so the autouse fixture resolves and the ~100 server tests in its scope stop failing. 3. tests/server/test_tools_kitchen_preflight.py: the two inspect.getsource tests assumed the preflight reference would be on the facade module source itself. After decomposition it lives in tools_kitchen/_open_kitchen.py and tools_fleet_dispatch/_handlers.py; pointed the inspections at the actual owning submodules. Co-Authored-By: Claude <noreply@anthropic.com>
…patch reach Adversarial validation of my facade re-exports surfaced two gaps: 1. tools_kitchen facade was missing _collect_disabled_feature_tags after commit 4d8b532 removed the recursion-causing wrapper but also dropped the re-export. Tests using patch("autoskillit.server.tools.tools_kitchen._collect_disabled_feature_tags") would have failed. Import from autoskillit.core.feature_flags directly to avoid re-introducing the recursion shadow. 2. _lifespan facade was missing _asyncio for the monkeypatch.setattr("autoskillit.server._lifespan._asyncio.sleep", ...) pattern at tests/server/test_lifespan.py:339. Added a top-level `import asyncio as _asyncio` so the patch resolves to the same module the submodules import. Co-Authored-By: Claude <noreply@anthropic.com>
Adversarial validation caught that the prior docstring incorrectly stated the auto-gate boot dispatch runs 'after the transport opens'; the dispatch actually runs BEFORE the lifespan yields (gate is enabled before the first tool call arrives), as documented in _session_boots.py and the lifespan body itself. Also restores the ':' lead-in for the submodule bullet list and re-includes the startup-checks concern that finding 7 explicitly asked to be named.
Adversarial validation caught that the prior docstring scoped the package to 'run_cmd and run_python' but the package also serves run_skill — _skill_contract.py is dedicated to it, and the docstring itself lists 'skill-contract lifecycle helpers' as a category, which contradicted the narrower scope statement.
#4663 decomposition The decomposition replaced 8 oversized modules with package directories. The __init__.py facades re-exported the @mcp.tool()-decorated entry points but omitted several internal helpers that test suites pin via mock.patch and monkeypatch.setattr at the package path. Re-export the missing names so the patch targets resolve: - _response_budget: _delivery_bound_summary, logger - tools_kitchen: __version__, logger, resolve_kitchen_id, etc. - tools_fleet_dispatch: _require_fleet - tools_pipeline_tracker: read_tracker_authority - _lifespan: validate_plugin_cache_hooks, write_readiness_sentinel, etc. - _execution_helpers: SkillContract, summarize_capture, logger
…l packages After the #4663 decomposition, MCP tool handlers live in server/tools/<name>/_X.py instead of flat server/tools/<name>.py files. The docs-count tests used a non-recursive glob("tools_*.py") that walked only the parent directory and silently dropped every tool defined inside a decomposed package (the missing counts add up to 11 tools: 3 kitchen-tagged, 8 free-range). Switch the four tool-count helpers to rglob("tools_*.py") so they descend into the new subpackage layout. Co-Authored-By: Claude <noreply@anthropic.com>
…ispatch The #4663 decomposition moved dispatch_food_truck/record_gate_dispatch's implementation into _handlers.py, but several tests still patch symbols via the tools_fleet_dispatch package facade (mock.patch("...tools_fleet_dispatch.X")). Since _handlers.py imported execute_dispatch, _require_fleet, and find_caller_session_id directly by name instead of resolving them through the package at call time, those patches never reached the real call sites. Route all three through a self-import of the tools_fleet_dispatch package (matching the existing precedent in tools_pipeline_tracker/_handlers.py) and re-export find_caller_session_id from the package facade.
…omposition
Many _lifespan tests patch symbols via the package facade
(mock.patch("...server._lifespan.X")), but the decomposed submodules
(_lifespan.py, _session_boots.py, _startup_checks.py) imported those same
symbols directly by name and called them as bare names — a decomposition
regression, since patching the flat module's own attribute used to reach
every call site for free.
Route _get_ctx_or_none, cleanup_readiness_sentinel, iter_all_scope_paths,
validate_plugin_cache_hooks, repair_broken_plugin_cache_hooks,
_write_hook_config, discover_campaign_state_files, reap_stale_dispatches_async,
register_active_kitchen, resolve_kitchen_id, create_background_task,
_collect_disabled_feature_tags, and _run_backend_mcp_registration_async
through a self-import of the _lifespan package at call time. Also fixes two
genuine __init__.py re-export gaps (discover_campaign_state_files and
cleanup_readiness_sentinel were never exposed on the package facade at all)
and simplifies several core.* re-exports to the gateway-level import already
used elsewhere in this same file instead of a redundant deep-module import.
Tests patch atomic_write, _artifact_path, _project_json_object, _emit_response_budget_event, build_post_effect_segment_failure, RecipeSegmentDeliveryError, and logger via the _response_budget package facade, but none of these were re-exported by __init__.py and _enforce.py / _primitives.py called them as directly-imported bare names — so the patches never reached the real call sites (tests/server/test_response_backstop.py, test_track_response_size.py). Add the missing re-exports and route the corresponding call sites in _enforce.py and _primitives.py through a self-import of the _response_budget package, matching the pattern already used across the rest of the #4663 decomposition.
…e fixes declare_join_batch was defined in the original flat tools_kitchen.py but was never accounted for anywhere in the #4663 plan's decomposition inventory — it was silently dropped, leaving TOOL_REGISTRY pointing at a handler that no longer existed anywhere in the server tree (tests/recipe/test_rules_tools.py::test_tool_params_matches_mcp_handler_signatures, plus the doc-count and registry-parity scanners). Restore it as a new tools_kitchen/_declare_join_batch.py submodule extracted verbatim from develop's tools_kitchen.py, re-exported from the package facade, and add it to the sibling-set guard test and the REQ-IMP-007 import-path allowlist. Also fixes the broader facade-reachability pattern across the rest of the tools_kitchen package: several submodules (_open_kitchen.py, _close_kitchen.py, _get_recipe.py, _reload_session.py, _open_kitchen_transition.py, _tracker_authority.py) imported package-facade-patched symbols (e.g. initialize_kitchen_tracker, try_retire_tracker, unregister_active_kitchen, and other cross-submodule helpers) directly by name instead of resolving them through the package at call time, so mock.patch against the tools_kitchen facade never reached the real call sites. Routes those calls through a self-import of the tools_kitchen package, and eliminates two now- unnecessary deferred (circular-break) imports in _open_kitchen.py in favor of the already-present module-level import of the same submodule.
…e_tracker
_handlers.py and _authority.py imported read_tracker_authority,
mark_step_complete, _handle_init, and _handle_status directly by name and
called them as bare names, so tests patching
mock.patch("...tools_pipeline_tracker.X") never reached the real call sites
(tests/server/test_record_pipeline_step.py,
tests/server/test_pipeline_tracker_authority.py,
tests/integration/test_pipeline_step_completion_flow.py). Also restores
try_retire_tracker as a facade re-export for the sibling tools_kitchen
package's own lease-release path exercised by
test_kitchen_release_does_external_work_outside_lease_lock.
Routes these through the package self-import already established in
_handlers.py (extended to _authority.py), matching the pattern used
throughout the rest of the #4663 decomposition.
…lpers
_run_cmd_spill.py and _run_python_coercion.py imported summarize_capture,
_process_capture_stream, and a private logger directly by name, so tests
patching mock.patch("...tools._execution_helpers.X") never reached the real
call sites (tests/server/test_tools_execution_spill.py,
tests/server/test_tools_run_cmd.py). Also restores SkillInput/SkillOutput as
facade re-exports (dropped during decomposition, alongside the already-
present SkillContract) for tests/server/test_tools_execution_results.py.
Routes the affected call sites through a self-import of the
_execution_helpers package, matching the pattern used throughout the rest
of the #4663 decomposition.
Separately, re-exports the shared `time` module from
_evidence_reader/__init__.py so tests can monkeypatch time.time via the
package facade (tests/server/test_tools_evidence_reader.py) — every
_evidence_reader submodule does its own `import time`, all referencing the
same sys.modules singleton, so patching the facade's copy already reaches
every call site once the attribute exists.
Several test-side static scanners assumed server/tools/*.py entry-point modules were always flat files and used a non-recursive glob (or a tools_*.py-only filename filter), so tool handlers that moved into a directory package during the #4663 decomposition became invisible to them: - test_response_backstop_parity.py's _iter_server_module_trees() used a flat glob("*.py") over server/ and server/tools/, missing open_kitchen's meta=response_backstop_tool_meta(...) attachment entirely (it now lives in tools_kitchen/_open_kitchen.py). Switched to rglob. - test_tool_registry_parity.py's _handler_signatures() only matched tools_*.py files, missing every handler inside tools_kitchen/, tools_fleet_dispatch/, and tools_pipeline_tracker/ (all now directories). Extended to also walk .py files inside decomposed tools_*/ packages. - test_doc_counts.py's _count_mcp_tools/_count_free_range_tools/ _count_headless_tools had the same tools_*.py-only blind spot. Added a shared _tools_entrypoint_files() helper used by all three. Also updates two arch guards whose hardcoded path/exemption tables predate the decomposition and would otherwise report false violations against the now-correct decomposed layout: - test_import_paths.py's REQ-IMP-007 allowlist gains tools_kitchen/_declare_join_batch.py (needs the same hooks._join_ledger / hooks._hook_settings cross-package access the flat file always had). - test_layer_enforcement.py's REQ-ARCH-001 cross-package-submodule-import exemption is repointed from the retired flat tools_kitchen.py path to the new tools_kitchen/_declare_join_batch.py location. - test_no_path_cwd_in_tools.py and test_subpackage_isolation.py: minor line- length wraps and the sibling-set guard gains _declare_join_batch.
…T guard test_boot_step_symmetry.py's _function_body_contains_symbol/_first_symbol_line only matched bare ast.Name references, so my facade-reachability fixes in _session_boots.py and tools_kitchen/_open_kitchen.py (routing reap_stale_dispatches_async, register_active_kitchen, and _write_hook_config through a self-imported package instead of a bare name) made this guard falsely report the boot steps as missing. Generalize both helpers to also match ast.Attribute nodes whose .attr equals the target symbol, mirroring the same bare-name-or-attribute acceptance test_response_backstop_parity.py already uses for the same reason.
…nt guard
Same root cause as the boot-step-symmetry guard: this scanner only matched
a bare resolve_kitchen_id() call (getattr(node.value.func, "id", ...)), so
_session_boots.py:87's ctx.kitchen_id = _lifespan_pkg.resolve_kitchen_id()
(needed for mock.patch("...server._lifespan.resolve_kitchen_id") reachability)
was flagged as a non-canonical assignment.
Accept the attribute-call form (func.attr) alongside the bare-name form
(func.id).
Trecek
force-pushed
the
impl-issue-4663-decompose-server-tools-20260817-124812
branch
from
August 18, 2026 23:48
95f8808 to
f205d34
Compare
…facade for monkeypatch reach
- tools_fleet_dispatch/_handlers.py referenced progress_heartbeat by its
direct module import instead of via the tools_fleet_dispatch package
facade, unlike the sibling execute_dispatch call on the same line. The
package __init__.py also never re-exported progress_heartbeat, so
tests/server/test_progress_heartbeat_wiring.py's
monkeypatch.setattr("...tools_fleet_dispatch.progress_heartbeat", ...)
raised AttributeError.
- tools_kitchen/_lock_ingredients.py called update_overlay via its direct
submodule import instead of the tools_kitchen package facade (the
established late-binding-for-monkeypatch-reach convention used
elsewhere in this package), so
tests/server/test_tools_config.py::test_config_and_ingredient_writers_preserve_each_others_keys
never observed the patched write, causing its cross-thread event wait
to time out.
Trecek
deleted the
impl-issue-4663-decompose-server-tools-20260817-124812
branch
August 19, 2026 01:18
Trecek
added a commit
that referenced
this pull request
Aug 19, 2026
…er develop merge Merging origin/develop (#4676, #4679 decomposed execution/backends and server/_lifespan.py into directory packages) shifted the file:line locations this registry hardcodes for known forwarding sites and unresolved dynamic reads, without changing the sites themselves. - server/_lifespan.py:747 -> server/_lifespan/_session_boots.py:455 (file split, same EVIDENCE_READER_ENV_FORWARD_VARS dict comprehension) - claude.py/codex.py _HEADLESS_EXCLUSIVE_VARS / _INTERACTIVE_ENV_EXCLUSIONS forwarding sites shifted within their files; codex.py's maintenance/ version-probe dict(os.environ) site moved into the new execution/backends/_codex_probes.py module Verified each new site against the live source before updating its justification; all four ambient-env-surface pincer tests pass locally after the merge.
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
Decomposes 7 of the 8 oversized source files in
src/autoskillit/server/referenced in issue #4663 into directory packages with submodules, each ≤ 750 lines. The remaining file (tools_execution.py, 2782 lines) is deferred to a separate PR per the plan's Phase 4 follow-up — see issue #4677.What changed
Each oversized flat module was moved into a directory package with an
__init__.pyfacade re-exporting every public symbol, so consumer imports keep working unchanged.server/_response_budget.pyserver/_response_budget/server/tools/_execution_helpers.pyserver/tools/_execution_helpers/server/tools/_evidence_reader.pyserver/tools/_evidence_reader/server/tools/tools_kitchen.pyserver/tools/tools_kitchen/server/tools/tools_fleet_dispatch.pyserver/tools/tools_fleet_dispatch/server/tools/tools_pipeline_tracker.pyserver/tools/tools_pipeline_tracker/server/_lifespan.pyserver/_lifespan/Total: 7,983 LoC moved into 38 new files. All files ≤ 750 lines (except
tools_kitchen/_open_kitchen.pyat 968 lines, intrinsic to theopen_kitchenfunction body).Test updates
test_<package>_decomposition_has_expected_siblings) intests/arch/test_subpackage_isolation.pythat pin each package's exact submodule set.rglobover the new packages:test_boot_step_symmetry.py,test_kitchen_id_assignment.py,test_startup_budget.py,test_exploration_request_identity_ownership.py,test_lifespan_readiness_structural.py,test_durable_artifact_writers_guard.py,test_import_paths.py._SINGLETON_SAFE_ASSIGNMENTStuple (line 174) now points at_open_kitchen_transition.pywhere_OPEN_KITCHEN_REQUEST_CTXlives after decomposition.Exemption retirement
Three obsolete entries deleted from
_LINE_LIMIT_EXEMPTIONS:_response_budget.py(E12)server/tools/_execution_helpers.py(E20)tools_kitchen.py(E7)The remaining
tools_execution.py(E18) exemption will be retired by the Phase 4 follow-up PR (#4677).Architectural deviations from the original plan
_authority.py(leaf-most module) instead of the plan's_startup.py. This breaks the unavoidable circular import between_authorityand_startupwithout resorting to function-level late imports.invocation_member_nameshome): Per the plan listed it as a_skill_contractre-export, but the function actually lives at line 227 of the source (in dispatch-metadata range). It re-exports from_dispatch_metadata.pycorrectly._open_kitchen.pyimports helpers from direct submodule paths (rather than the package facade) to break the load-time circular dependency. The facade still re-exports every helper for external test monkey-patching.🤖 Generated with Claude Code via AutoSkillit