compose function
Resolves capability contributions into a deterministic operation set
(R-204):
- 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.
- 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(),
);
}