cc-os/plugins/os-adr/lib/adr/migration_report.rb

68 lines
2.5 KiB
Ruby
Raw Normal View History

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
<!-- Generated by os-adr migration. Regenerated on every migration pass. -->
# 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