runPipeline function

String runPipeline({
  1. required List<ScannedFile> files,
  2. AdapterConfig config = const AdapterConfig(),
  3. required void warn(
    1. String
    ),
  4. AnnotationResolution? resolution,
  5. String adapterName = cliAdapterName,
})

Pipeline auf bereits gescannten Dateien — gemeinsame Basis von Adapter-CLI und build_runner-Builder (Weg D). Gleiche Merge-/Sortier-/ Serialisierungslogik für beide Zubringer (Paritätsgarantie): Unterschiede entstehen nur durch resolution (zusätzlich aufgelöste Werte) und adapterName (meta.adapters-Eintrag).

Implementation

String runPipeline({
  required List<ScannedFile> files,
  AdapterConfig config = const AdapterConfig(),
  required void Function(String) warn,
  AnnotationResolution? resolution,
  String adapterName = cliAdapterName,
}) {
  final errors = <String>[];
  final nodes = <GraphNode>[];
  final flows = <GraphFlow>[];
  final actions = <ActionCandidate>[];
  final manualScreenClasses = <String, String>{};

  // Weg A + B — manuelle Quellen, Dateien in sortierter Reihenfolge.
  for (final file in files) {
    for (final extraction in [
      parseComments(file, warn, errors),
      extractAnnotations(file, warn, errors, resolution: resolution),
    ]) {
      nodes.addAll(extraction.nodes);
      flows.addAll(extraction.flows);
      actions.addAll(extraction.actions);
      extraction.screenClassNames.forEach(
          (cls, id) => manualScreenClasses.putIfAbsent(cls, () => id));
    }
  }

  // Weg C — Ableitungen.
  var autoRoute = AutoRouteDerivation();
  if (config.deriveAutoRoute) {
    autoRoute = deriveAutoRoute(files, warn);
    nodes.addAll(autoRoute.nodes);
  }

  final edges = <GraphEdge>[];
  var builderClassToScreen = const <String, String>{};
  if (config.deriveGoRouter) {
    final goRouter = deriveGoRouter(
      files,
      warn,
      manualScreenClasses: manualScreenClasses,
      extraClassToScreen: autoRoute.classToScreen,
      extraPathToScreen: autoRoute.pathToScreen,
    );
    nodes.addAll(goRouter.nodes);
    flows.addAll(goRouter.flows);
    edges.addAll(goRouter.edges);
    builderClassToScreen = goRouter.builderClassToScreen;
  }

  // from-Inferenz für Actions ohne explizites `from` (DD §B.3): umschließende
  // Klasse muss als Screen bekannt sein (Annotation, Kommentar oder
  // builder-/auto_route-Zuordnung).
  for (final action in actions) {
    var from = action.from;
    if (from == null) {
      final cls = action.enclosingClassName;
      from = cls == null
          ? null
          : manualScreenClasses[cls] ??
              autoRoute.classToScreen[cls] ??
              builderClassToScreen[cls];
      if (from == null) {
        errors.add('${action.sourceRef}: Action "${action.label}" ohne "from" '
            '— umschließende Klasse ${cls ?? '(keine)'} ist kein bekannter '
            'Screen.');
        continue;
      }
    }
    edges.add(GraphEdge(
      id: action.id,
      from: from,
      to: action.to,
      trigger: action.trigger,
      label: action.label,
      condition: action.condition,
      source: SourceKind.annotation,
      sourceRef: action.sourceRef,
    ));
  }

  if (errors.isNotEmpty) {
    throw AdapterException(errors);
  }

  final merged = mergeGraph(nodes: nodes, edges: edges, flows: flows);
  return encodeCanonicalGraph(
    flows: merged.flows,
    nodes: merged.nodes,
    edges: merged.edges,
    adapterName: adapterName,
  );
}