Skip to content

fix: bring workflow_graph_ast_* infrastructure under the 40-line/method cap - #279

Merged
cdeust merged 1 commit into
mainfrom
fix-split-workflow-graph-source-ast-275
Jul 30, 2026
Merged

fix: bring workflow_graph_ast_* infrastructure under the 40-line/method cap#279
cdeust merged 1 commit into
mainfrom
fix-split-workflow-graph-source-ast-275

Conversation

@cdeust

@cdeust cdeust commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Summary

Issue #275 (the 300-line/file cap on workflow_graph_source_ast.py) was
already resolved on main by PR #278's follow-up commit (0702b77) — an
independent split into ap_sync_loop.py / workflow_graph_ast_symbols.py
/ workflow_graph_ast_edges.py / workflow_graph_ast_response.py landed
and closed #275 while this branch was still based on the pre-#278 tree.
Rebasing this branch's own (differently-named) split onto that state would
just duplicate already-merged work and conflict with it — docs/module- inventory.md already lists all four modules — so this PR now keeps
main's file layout and instead fixes what was actually wrong: a false
ledger row in this PR's prior revision claimed "§4.2 pass" while AST
measurement found 4 functions over the 40-line/method cap (up to 53
lines), and once the rebase touched these files, AST measurement found 3
more pre-existing violations in the already-merged #278 split (up to 193
lines) — boy-scout material in the same touched files (coding-standards.md
§14).

Pure extract-method, behavior-preserving, no file renames/moves:

File Split
ap_sync_loop.py _drain_pending_tasks (90) → _loop_is_drainable (guard) + _run_task_drain/_cancel_and_await_pending (schedule/await)
workflow_graph_ast_response.py as_list (46) → _zip_columns_rows + _from_legacy_shape; added build_path_tails (was duplicated inline in both symbols.py and edges.py — extracted once)
workflow_graph_ast_symbols.py _symbol_type_from_label (41) → a dispatch dict (_LABEL_TO_SYMBOL_TYPE, OCP-friendly over an if-chain); symbol_batches_async (127) → _where_for_tails/_build_symbol_query/_parse_symbol_batch; verify_symbols_async (44) → _collect_all_symbol_names/_qualname_matches
workflow_graph_ast_edges.py edge_batches_async (193) → rel-table enumeration split by family (_calls_and_member_rel_tables/_import_rel_tables/_uses_rel_tables) + _run_edge; _run_edge (94) → _build_edge_query/_edge_row_to_dict; _edge_row_to_dict (46) → _split_edge_endpoints/_parse_provenance

Public import paths are unchanged (WorkflowGraphASTSource, _SyncLoop,
_SYMBOL_LABELS all still resolve from workflow_graph_source_ast, which
this PR does not touch — it had no over-cap functions).

Completion Ledger (coding-standards.md §13.2)

Item Evidence
§4.1 file ≤300 lines, all 5 infra files AST measurement (command below): every file ≤300 lines — command output quoted in "Verification"
§4.2 method ≤40 lines, all functions in all 5 infra files AST measurement: every function ≤40 lines (the claim this PR was refused for is now TRUE by measurement, not restated)
_drain_pending_tasks behavior unchanged tests_py/infrastructure/test_workflow_graph_source_ast.py::TestDrainPendingTasks (5 tests) — all pass unmodified
_SyncLoop construction/close/wedge-timeout contract unchanged TestSyncLoopConstruction, TestSyncLoopCloseRealLoop, TestBoundedWaitTimeout, TestSingleReaderOwnership — all pass unmodified
as_list/build_path_tails normalization unchanged TestIncrementalYield, TestNoFullMaterialization (exercise as_list indirectly via iter_symbols/iter_ast_edges) — pass unmodified
_symbol_type_from_label dispatch-dict equivalence Verified interactively (function→function, struct→class, Import→import) — same 3 branches as the original if-chain, now table-driven
symbol_batches_async/verify_symbols_async per-graph skip-on-error unchanged tests_py/infrastructure/test_ble001_sweep_infrastructure.py::TestWorkflowAstGraphSkips — pass unmodified
edge_batches_async/_run_edge unchanged TestIncrementalYield::test_edges_yield_incrementally — pass unmodified
No regression from the refactor Paired mutmut comparison, same 3 test files, main's pre-refactor originals vs. this PR's files — see "Mutation testing" below

Verification

$ python3 - <<'PY'
import ast, pathlib
files = ["mcp_server/infrastructure/ap_sync_loop.py",
         "mcp_server/infrastructure/workflow_graph_ast_edges.py",
         "mcp_server/infrastructure/workflow_graph_ast_symbols.py",
         "mcp_server/infrastructure/workflow_graph_ast_response.py",
         "mcp_server/infrastructure/workflow_graph_source_ast.py"]
for f in files:
    src = pathlib.Path(f).read_text(); n = len(src.splitlines())
    assert n <= 300, f"{f}: {n} lines"
    for node in ast.walk(ast.parse(src)):
        if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
            ln = (node.end_lineno or node.lineno) - node.lineno + 1
            assert ln <= 40, f"{f}::{node.name}: {ln} lines"
print("ALL CLEAR")
PY
ALL CLEAR
  • ruff check + ruff format --check: clean on all touched files.
  • pyright: 0 errors/warnings on all 5 infra files + the two consuming
    handlers (unified_search.py, wiki_verify.py).
  • pytest full suite: 6751 passed, 5 skipped, 0 modified/added (pure
    refactor — same behavior, same tests).
  • scripts/check_doc_claims.py / scripts/generate_repo_badges.py --check: both green, untouched (issue fix: eliminate the six-file test-count conflict class with a monotone floor (issue #293) #294's monotone floor is
    authoritative on main; this PR does not edit any doc/badge file).

Mutation testing (§12) — paired before/after comparison

Scoped mutmut run (the 5 infra files vs. test_workflow_graph_source_ast.py

main (pre-refactor) This PR (post-refactor)
Total mutants 876 776
Killed 244 299
Survived 460 272
No-tests-in-scope 168 199
Timeout 4 6

The refactor reduces survivors (460→272) and increases kills
(244→299) — consistent with deduplicating the path-tails logic
(build_path_tails, previously copy-pasted in both symbols.py and
edges.py) and replacing _symbol_type_from_label's if-chain with a
dispatch dict (fewer independently-mutable branches). No regression.

The remaining ~272 survivors are a pre-existing, unchanged-in-kind
gap: this suite (by its own docstring) targets the streaming/ordering/
bounded-wait contract, not Cypher query-string construction or
edge/symbol row-parsing detail — identically true before and after this
PR (main's pre-refactor score already showed the same 460-survivor
shape). Closing it is a dedicated test-hardening effort (~300 new
assertions across query construction and row parsing), out of scope for
a behavior-preserving cap-compliance fix (coding-standards.md §15.1: a
large prerequisite is sequenced as its own PR, not mixed into this one).

Test plan

  • pytest tests_py/infrastructure/test_workflow_graph_source_ast.py tests_py/infrastructure/test_ble001_sweep_infrastructure.py tests_py/infrastructure/test_s110_sweep_infrastructure.py — 64 passed, 0 modified, 0 added
  • Full suite: 6751 passed, 5 skipped
  • ruff check + ruff format --check clean
  • pyright 0 diagnostics
  • AST §4.1/§4.2 measurement — all 5 files clean (see "Verification")
  • Paired mutation-testing comparison — no regression (see above)
  • Live import smoke test: WorkflowGraphASTSource, _SyncLoop,
    _SYMBOL_LABELS, symbol_batches_async, verify_symbols_async,
    edge_batches_async, as_list, build_path_tails all resolve

Co-Authored-By: Claude noreply@anthropic.com

…od cap

Issue #275 (the 300-line/file cap on workflow_graph_source_ast.py) was
already resolved on main by PR #278's follow-up commit (0702b77) — an
independent split into ap_sync_loop.py / workflow_graph_ast_symbols.py /
workflow_graph_ast_edges.py / workflow_graph_ast_response.py landed and
closed #275 before this branch was rebased. Recreating this PR's own
(differently-named) split would just duplicate already-merged work and
conflict with it, so this commit keeps main's file layout and instead
fixes what #278's split left over-cap at the METHOD level (CLAUDE.md's
40-line/method cap, docstrings included) — the defect PR #279 was
refused for: a false ledger row claiming "§4.2 pass" while AST
measurement found 4 functions over cap (up to 53 lines), and, once this
branch's own rebase touched these files, 3 more pre-existing violations
in already-merged code (up to 193 lines) surfaced as boy-scout material
in the same touched files (coding-standards.md §14).

Pure extract-method, behavior-preserving:
- ap_sync_loop.py: _drain_pending_tasks (90) -> guard (_loop_is_drainable)
  + scheduling (_run_task_drain/_cancel_and_await_pending).
- workflow_graph_ast_response.py: as_list (46) -> _zip_columns_rows +
  _from_legacy_shape; added build_path_tails (was duplicated inline in
  both symbols.py and edges.py — extracted once, DRY).
- workflow_graph_ast_symbols.py: _symbol_type_from_label (41) -> a
  dispatch dict (_LABEL_TO_SYMBOL_TYPE, OCP-friendly over an if-chain);
  symbol_batches_async (127) -> _where_for_tails/_build_symbol_query/
  _parse_symbol_batch; verify_symbols_async (44) ->
  _collect_all_symbol_names/_qualname_matches.
- workflow_graph_ast_edges.py: edge_batches_async (193) -> rel-table
  enumeration split by family (_calls_and_member_rel_tables/
  _import_rel_tables/_uses_rel_tables) + _run_edge; _run_edge (94) ->
  _build_edge_query/_edge_row_to_dict; _edge_row_to_dict (46) ->
  _split_edge_endpoints/_parse_provenance.

Verification:
- AST measurement: every function in all 5 files is <=40 lines, every
  file <=300 lines (ledger claim now TRUE by measurement, not asserted).
- ruff check + ruff format --check: clean on all touched files.
- pyright: 0 diagnostics on all touched files + the two consuming
  handlers (unified_search.py, wiki_verify.py).
- Full suite: 6751 passed, 5 skipped, 0 modified/added (pure refactor).
- Paired mutation-testing comparison (mutmut, same 3 test files) proves
  no regression: main's pre-refactor originals scored 244 killed / 460
  survived / 876 total; this refactor scores 299 killed / 272 survived
  / 776 total — strictly more kills, strictly fewer survivors (dedup via
  build_path_tails + the dispatch-dict reduces mutable branch surface).
  The remaining ~272 survivors are a pre-existing, unchanged-in-kind gap
  (this suite targets the streaming/bounded-wait contract, not Cypher
  query-string construction or row-parsing detail — same class of gap
  the original PR's own before/after comparison already reported);
  closing it is a dedicated test-hardening effort, out of scope for a
  behavior-preserving cap-compliance fix (coding-standards.md §15.1).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Yu6EnWspTfqHoGkExyS6u
@cdeust
cdeust force-pushed the fix-split-workflow-graph-source-ast-275 branch from a2f7522 to 1d0f4a7 Compare July 30, 2026 13:28
@cdeust cdeust changed the title refactor: split workflow_graph_source_ast.py under the 300-line cap (#275) fix: bring workflow_graph_ast_* infrastructure under the 40-line/method cap Jul 30, 2026
@cdeust
cdeust merged commit 47e2e25 into main Jul 30, 2026
19 checks passed
@cdeust
cdeust deleted the fix-split-workflow-graph-source-ast-275 branch July 30, 2026 13:50
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.

refactor: workflow_graph_source_ast.py exceeds the 300-line file cap (1029 lines)

1 participant