Improves Captain V1 → V2 migration for complex legacy instructions so mandatory triggers, workflows, language rules, and escalation behavior remain active while query-dependent product knowledge is prepared as pending FAQ candidates. ## What changed - Added explicit preservation rules for mandatory triggers, verification steps, escalation conditions, exceptions, and language behavior. - Added an auditor that checks the draft and fixes any issues before manual review. - Kept the existing migration application contract and schema limits unchanged - Added focused regression coverage for the complex-prompt classifier contract. ## How to reproduce Generate a migration draft for an assistant with dense legacy instructions containing mandatory handoff triggers, verification rules, product facts, and multi-step workflows. The resulting draft should keep actions active, place query-dependent facts in FAQ candidates, and avoid silently dropping or reversing source requirements. Focused Captain migration specs and RuboCop checks pass locally.
37 lines
1.1 KiB
Ruby
37 lines
1.1 KiB
Ruby
class Captain::AssistantMigration::FaqApplier
|
|
pattr_initialize [:assistant!, :candidates!]
|
|
|
|
def changes
|
|
@changes ||= candidates.each_with_object({ create: [] }) do |candidate, result|
|
|
categorize(candidate, result)
|
|
end.compact_blank.presence
|
|
end
|
|
|
|
def apply(changes)
|
|
Array(changes[:create]).each do |candidate|
|
|
assistant.responses.create!(candidate.slice('question', 'answer', 'status'))
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def categorize(candidate, result)
|
|
existing_answers = assistant.responses.approved.where(question: candidate['question']).pluck(:answer)
|
|
planned_answers = result[:create].filter_map do |response|
|
|
response['answer'] if response['question'] == candidate['question']
|
|
end
|
|
answers = existing_answers + planned_answers
|
|
|
|
ensure_no_conflict!(candidate, answers)
|
|
return if answers.include?(candidate['answer'])
|
|
|
|
result[:create] << candidate.merge('status' => 'approved')
|
|
end
|
|
|
|
def ensure_no_conflict!(candidate, answers)
|
|
return if answers.all?(candidate['answer'])
|
|
|
|
raise ArgumentError, "FAQ candidate conflicts with an existing FAQ: #{candidate['question']}"
|
|
end
|
|
end
|