Skip to content

Reshape analysis levels as a fluent AnalysisPipeline - #110

Open
rahlk wants to merge 14 commits into
mainfrom
refactor/analysis-pipeline-fluent-passes
Open

rahlk wants to merge 14 commits into
mainfrom
refactor/analysis-pipeline-fluent-passes

Conversation

@rahlk

@rahlk rahlk commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

What

Reshape the analysis levels: refactor Codeanalyzer.analyze() — a ~130-line procedural method with if self.analysis_level >= N gates threaded throughout — into a fluent AnalysisPipeline of four subsystem-grouped passes over a shared AnalysisContext.

analysis = (
    AnalysisPipeline(ctx)
        .with_symbol_table()        # syntactic_analysis/   (L1 tree)
        .with_call_graph()          # semantic_analysis/ + identity (L1 + L2)
        .with_intraproc_dataflow()  # dataflow/  L3
        .with_interproc_dataflow()  # dataflow/  L4
        .build()                    # -> Analysis
)

This is a behavior-preserving refactor — no schema change, no CLI change, no output change. schema_version stays 2.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, testable context -> context pass with uniform timing/gate logging.

Design

  • Passes self-gate on an intrinsic min_level (1, 1, 3, 4) via one shared _run gate — no skip argument 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.
  • AnalysisContext carries the working state threaded through the chain: inputs (options, project_dir, virtualenv, analysis_level, app_name, cached_symbol_table) and produced artifacts (symbol_tableapp/sig_to_idinfosir). The L4 pass reuses the L3 PDGs (infos) rather than recomputing.
  • Cache and venv lifecycle stay on Codeanalyzer (not in the pipeline), so every pass is a pure context -> context function testable against a hand-built context. Each pass asserts its preconditions, so a misordered chain fails loudly.
  • New codeanalyzer/pipeline/ package (context.py, symbol_table.py, passes.py, pipeline.py) — imports only downward (never from core; core imports from pipeline). Three helpers (build_symbol_table, pycg_call_graph_edges, home_external_symbols) moved out of Codeanalyzer as free functions.

Full rationale in docs/superpowers/specs/2026-07-22-analysis-pipeline-fluent-passes-design.md; task-by-task plan in docs/superpowers/plans/2026-07-22-analysis-pipeline-fluent-passes.md.

Behavior-preservation evidence

  • Characterization golden gate (test/test_pipeline_equivalence.py): analysis.json for four fixtures × levels was captured from the old analyze(), committed, and now matches the new pipeline byte-for-byte (L1–L3). Volatile fields (analyzer.version, repository, absolute file_path/path/last_modified) are normalized so the gate is deterministic across runs.
  • Degradation semantics preserved verbatim (PyCG failure → Jedi-only; python-scalpel absent → type-based alias fallback).
  • Monotonicity (L1 ⊆ L2 ⊆ L3 ⊆ L4) and determinism (PYTHONHASHSEED=0 + sorted call graph) unchanged.

Test status

  • The behavior-preservation suites are green: the equivalence gate, test_pipeline.py (pass/pipeline units, no mocks), and the test_v2_* / test_dataflow_* / test_neo4j_schema / test_env_interpreter suites.
  • Pre-existing, unrelated: test/test_cli.py has ~6 failures (PyCG edge-count assertions + one --version test) that fail on main as well (a PyCG/env issue on Python 3.14). This branch introduces zero new failures.

L4 coverage caveat

The -a 4 equivalence goldens require the optional python-scalpel dependency; 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 in test_pipeline.py. Follow-up suggestion: add a scalpel-installed CI lane to capture and commit the a4 goldens, making "byte-identical at every level" literally enforced.

rahlk added 14 commits July 22, 2026 10:33
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.
@rahlk rahlk added the refactor Behaviour-preserving restructuring label Sep 15, 2026
@rahlk rahlk changed the title refactor: reshape analysis levels as a fluent AnalysisPipeline Reshape analysis levels as a fluent AnalysisPipeline Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactor Behaviour-preserving restructuring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant