module Adr # Generates docs/adr/migration-report.md โ€” the single aggregate surface for # migration uncertainty (alongside per-file migration_confidence frontmatter). class MigrationReport FILENAME = "migration-report.md" # Pilot-gate threshold (task 5.5): a run whose low-confidence flag rate # exceeds this is not good enough for wider rollout โ€” tighten heuristics # and re-run. Set empirically during the 2026-07-03 pilot. FLAG_RATE_THRESHOLD = 0.25 def initialize(repository:, detector:) @repository = repository @detector = detector end def path = File.join(@repository.dir, FILENAME) def write! File.write(path, content) end def migrated_entries @repository.entries.select { |e| e.record.frontmatter["migration_source"] } end def flag_rate entries = migrated_entries return 0.0 if entries.empty? low = entries.count { |e| e.record.frontmatter["migration_confidence"] == "low" } (low.to_f / entries.size).round(3) end def content entries = migrated_entries unrecognized = @detector.units.select { |u| u.shape == :unrecognized } rows = entries.map do |entry| record = entry.record pending = record.sections.select { |_, v| v == Migrator::PENDING_MARKER }.keys not_stated = record.sections.select { |_, v| v == Migrator::NOT_STATED_MARKER }.keys "| #{record.id} | [#{record.title}](#{File.basename(entry.path)}) " \ "| #{record.frontmatter['migration_source']} " \ "| #{record.frontmatter['migration_confidence']} " \ "| #{(pending.map { |s| "#{s} (pending)" } + not_stated.map { |s| "#{s} (not stated)" }).join(', ')} |" end <<~MD # ADR Migration Report Converted: #{entries.size} ยท Low-confidence flag rate: #{(flag_rate * 100).round(1)}% (gate threshold: #{(FLAG_RATE_THRESHOLD * 100).round}% โ€” #{flag_rate > FLAG_RATE_THRESHOLD ? 'EXCEEDED, tighten heuristics before wider rollout' : 'within gate'}) | ID | Title | Source | Confidence | Open items | | --- | --- | --- | --- | --- | #{rows.join("\n")} ## Unrecognized content (manual handling required) #{unrecognized.empty? ? '_none_' : unrecognized.map { |u| "- #{u.source_ref}" }.join("\n")} _Sources are untouched. Old-system removal is a separate, explicitly user-approved step._ MD end end end