refactor: reshape analysis levels as a fluent AnalysisPipeline#110
Open
rahlk wants to merge 14 commits into
Open
refactor: reshape analysis levels as a fluent AnalysisPipeline#110rahlk wants to merge 14 commits into
rahlk wants to merge 14 commits into
Conversation
Design for refactoring Codeanalyzer.analyze() into a fluent AnalysisPipeline of four subsystem-grouped passes over a shared AnalysisContext. Passes self-gate on an intrinsic min_level (no explicit skip arg); caching/venv stay at the edge; three helpers move off Codeanalyzer. Behavior-preserving: byte-identical output at every level, monotonicity preserved. Includes the four-layer testing plan.
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.
What
Reshape the analysis levels: refactor
Codeanalyzer.analyze()— a ~130-line procedural method withif self.analysis_level >= Ngates threaded throughout — into a fluentAnalysisPipelineof four subsystem-grouped passes over a sharedAnalysisContext.This is a behavior-preserving refactor — no schema change, no CLI change, no output change.
schema_versionstays2.0.0.Why
The old
analyze()was hard to read as "the four levels," the per-level work wasn't independently testable, adding/reordering a step meant editing a monolith, and per-step observability was hand-rolled. The pipeline makes each level a self-contained, testablecontext -> contextpass with uniform timing/gate logging.Design
min_level(1, 1, 3, 4) via one shared_rungate — noskipargument to pass wrong at a call site. Skips are observable (⏭️ intraproc_dataflow: skipped (level 2 < 3)); the two internal L2 gates (PyCG augmentation,backfill_callees) stay inside_pass_call_graph.AnalysisContextcarries the working state threaded through the chain: inputs (options,project_dir,virtualenv,analysis_level,app_name,cached_symbol_table) and produced artifacts (symbol_table→app/sig_to_id→infos→ir). The L4 pass reuses the L3 PDGs (infos) rather than recomputing.Codeanalyzer(not in the pipeline), so every pass is a purecontext -> contextfunction testable against a hand-built context. Each pass asserts its preconditions, so a misordered chain fails loudly.codeanalyzer/pipeline/package (context.py,symbol_table.py,passes.py,pipeline.py) — imports only downward (never fromcore;coreimports frompipeline). Three helpers (build_symbol_table,pycg_call_graph_edges,home_external_symbols) moved out ofCodeanalyzeras free functions.Full rationale in
docs/superpowers/specs/2026-07-22-analysis-pipeline-fluent-passes-design.md; task-by-task plan indocs/superpowers/plans/2026-07-22-analysis-pipeline-fluent-passes.md.Behavior-preservation evidence
test/test_pipeline_equivalence.py):analysis.jsonfor four fixtures × levels was captured from the oldanalyze(), committed, and now matches the new pipeline byte-for-byte (L1–L3). Volatile fields (analyzer.version,repository, absolutefile_path/path/last_modified) are normalized so the gate is deterministic across runs.python-scalpelabsent → type-based alias fallback).L1 ⊆ L2 ⊆ L3 ⊆ L4) and determinism (PYTHONHASHSEED=0+ sorted call graph) unchanged.Test status
test_pipeline.py(pass/pipeline units, no mocks), and thetest_v2_*/test_dataflow_*/test_neo4j_schema/test_env_interpretersuites.test/test_cli.pyhas ~6 failures (PyCG edge-count assertions + one--versiontest) that fail onmainas well (a PyCG/env issue on Python 3.14). This branch introduces zero new failures.L4 coverage caveat
The
-a 4equivalence goldens require the optionalpython-scalpeldependency; when it is absent those cases skip, so the byte-identical gate enforces L1–L3 in a scalpel-free environment. L4 behavior-preservation is instead covered by the interproc pass being a verbatim relocation of the old L4 block plus the structural L4 suites (test_v2_l4,test_v2_l4_summary,test_dataflow_sdg) and the L4 ordering test intest_pipeline.py. Follow-up suggestion: add a scalpel-installed CI lane to capture and commit thea4goldens, making "byte-identical at every level" literally enforced.