compose function

ComposedResult compose(
  1. List<Contribution> contributions
)

Resolves capability contributions into a deterministic operation set (R-204):

  1. De-duplicate by canonical identity (kind + canonicalKey) — an identical contribution from two capabilities collapses to one (FR-036, INV-OP1); a genuinely conflicting one throws ComposerConflictException rather than silently picking a winner.
  2. Order deterministically by a stable topological sort over Contribution.dependsOnRecipeRefs, ties broken by canonicalKey (FR-035, FR-037, INV-OP2) — the same (contributions) input always yields the same output sequence, across processes and days.

File-kind contributions become Operations (the unchanged 0.1 model, createFile only, per INV-O2); dependency/registration/init contributions are planning data the render/stage layer merges into the target files they belong to (pubspec.yaml, the DI container, main.dart) — composing bytes is out of scope here (plan.md's planning/rendering split).

Implementation

ComposedResult compose(List<Contribution> contributions) {
  final deduped = _deduplicate(contributions);
  final ordered = _stableTopologicalSort(deduped);

  return ComposedResult(
    fileOperations: [
      for (final c in ordered.where((c) => c.kind == ContributionKind.file))
        Operation(
          kind: OperationKind.createFile,
          logicalPath: c.canonicalKey,
          contentHash: computeRawHash(utf8.encode(c.content)),
          ownershipClass: OwnershipClass.flutterStartOwned,
        ),
    ],
    dependencyContributions: ordered
        .where((c) => c.kind == ContributionKind.dependency)
        .toList(),
    registrationContributions: ordered
        .where((c) => c.kind == ContributionKind.registration)
        .toList(),
    initContributions: ordered
        .where((c) => c.kind == ContributionKind.init)
        .toList(),
  );
}