package grammar import ( "context" "fmt" "gitea.maximumdirect.net/eric/audita/internal/core/schema" "gitea.maximumdirect.net/eric/audita/internal/framework/contracts" "gitea.maximumdirect.net/eric/audita/internal/framework/proposal_generation" "gitea.maximumdirect.net/eric/audita/internal/framework/proposals" builtinvalidators "gitea.maximumdirect.net/eric/audita/internal/validators" ) type Module struct { validators []contracts.Validator } func New() (*Module, error) { chain, err := builtinvalidators.ResolveBuiltInChain("grammar", builtinvalidators.NewBuiltInRegistry()) if err != nil { return nil, err } return &Module{ validators: chain, }, nil } func (m *Module) Key() string { return "grammar" } func (m *Module) ReplacementPolicy() proposals.ReplacementPolicy { // Conservative single-span replacement. return proposals.ReplacementPolicyRequireUnique } func (m *Module) Validators() []contracts.Validator { return append([]contracts.Validator(nil), m.validators...) } func (m *Module) Propose(ctx context.Context, req contracts.ProposalRequest) ([]proposals.CorrectionProposal, error) { sectionTranscript := transcriptForSection(req.WorkingTranscript, req.Section) sectionIndex := 0 if req.Section != nil { sectionIndex = req.Section.Index } transcriptDescription := "" if req.Config != nil { transcriptDescription = req.Config.TranscriptDescription } messages, err := BuildProposalMessages(sectionTranscript, req.Glossary, sectionIndex, transcriptDescription) if err != nil { return nil, err } generated, err := proposal_generation.GenerateCandidates(ctx, proposal_generation.Request{ ModuleKey: req.RunSpec.ModuleKey, ModuleInstance: req.RunSpec.InstanceName, ReplacementPolicy: req.RunSpec.ReplacementPolicy, WorkingTranscript: req.WorkingTranscript, Section: req.Section, Glossary: req.Glossary, Config: req.Config, Messages: messages, PromptMetadata: map[string]any{ "prompt_id": proposalPromptMetadata().PromptID, "prompt_version": proposalPromptMetadata().PromptVersion, "prompt_source": proposalPromptMetadata().PromptSource, "embedded_path": proposalPromptMetadata().EmbeddedPath, "sha256": proposalPromptMetadata().SHA256, }, StageName: proposalStageName(req), StartIndex: 0, LLMClient: req.LLMClient, Scheduler: req.LLMScheduler, DiagnosticsDir: req.DiagnosticsDir, }) if err != nil { return nil, err } return generated.Corrections, nil } func transcriptForSection(transcript *schema.Transcript, section *contracts.SectionMetadata) *schema.Transcript { if section == nil || transcript == nil { return transcript } segments := make([]schema.Segment, 0, len(transcript.Segments)) for _, seg := range transcript.Segments { if seg.ID >= section.StartSegmentID && seg.ID <= section.EndSegmentID { segments = append(segments, seg) } } return &schema.Transcript{Segments: segments} } func proposalStageName(req contracts.ProposalRequest) string { if req.Section == nil || req.Section.Index == 0 { return fmt.Sprintf("%s:proposal", req.RunSpec.InstanceName) } return fmt.Sprintf("%s:proposal:section-%04d", req.RunSpec.InstanceName, req.Section.Index) }