diff --git a/docs/roadmap/remediation.md b/docs/roadmap/remediation.md index 4d23d33..9c53845 100644 --- a/docs/roadmap/remediation.md +++ b/docs/roadmap/remediation.md @@ -707,6 +707,8 @@ decisions, and output order without rescanning decisions for each candidate. ## R20 — P3: Skip Apply-Time Copies for Sections With No Approved Edits +**Status:** Completed. Sections without approved proposals retain their existing segments. The runner still records every work unit, and sections with approved changes use the existing ordered application and protection checks. No-edit coverage verifies transcript and work-unit results. + **Category** Avoidable transcript copying during Stage 12 application. diff --git a/internal/framework/runner/runner.go b/internal/framework/runner/runner.go index d6e52b3..3785c84 100644 --- a/internal/framework/runner/runner.go +++ b/internal/framework/runner/runner.go @@ -254,6 +254,12 @@ func transcriptFromSections(sections []chunking.Section) *schema.Transcript { // applyWithinSections leaves section membership and initial metadata unchanged. // Approved proposals are applied only to their identified section. func applyWithinSections(sections []chunking.Section, approved []proposals.EnrichedCorrectionProposal, policy proposals.ReplacementPolicy, vocabulary validators.ProtectedVocabulary, allowRepetitionCleanup bool) ([]chunking.Section, []proposals.AppliedChange, []proposals.SkippedChange, []int, error) { + applied := make([]proposals.AppliedChange, 0) + skipped := make([]proposals.SkippedChange, 0) + appliedBySection := make([]int, len(sections)) + if len(approved) == 0 { + return sections, applied, skipped, appliedBySection, nil + } bySection := make(map[int][]proposals.EnrichedCorrectionProposal, len(sections)) known := make(map[int]struct{}, len(sections)) for _, section := range sections { @@ -270,11 +276,12 @@ func applyWithinSections(sections []chunking.Section, approved []proposals.Enric } updated := make([]chunking.Section, len(sections)) copy(updated, sections) - applied := make([]proposals.AppliedChange, 0) - skipped := make([]proposals.SkippedChange, 0) - appliedBySection := make([]int, len(sections)) for i, section := range sections { - result := proposals.ApplyProposals(transcriptFromSection(section), bySection[section.Index], policy, func(before, after string) bool { return vocabulary.AllowsChange(before, after, allowRepetitionCleanup) }) + candidates := bySection[section.Index] + if len(candidates) == 0 { + continue + } + result := proposals.ApplyProposals(transcriptFromSection(section), candidates, policy, func(before, after string) bool { return vocabulary.AllowsChange(before, after, allowRepetitionCleanup) }) updated[i].Segments = result.Transcript.Segments applied = append(applied, result.Applied...) skipped = append(skipped, result.Skipped...) diff --git a/internal/framework/runner/runner_test.go b/internal/framework/runner/runner_test.go index 313bc83..0c8f550 100644 --- a/internal/framework/runner/runner_test.go +++ b/internal/framework/runner/runner_test.go @@ -5,6 +5,7 @@ import ( "errors" "os" "path/filepath" + "reflect" "strconv" "strings" "sync" @@ -396,6 +397,14 @@ func TestRunnerProposalsExecutePerChunkSection(t *testing.T) { if len(out.ModuleResults) != 1 { t.Fatalf("expected one module result, got %+v", out.ModuleResults) } + if !reflect.DeepEqual(out.FinalTranscript, transcript) || len(out.WorkUnits) != 3 { + t.Fatalf("no-edit run changed the transcript or inventory: %+v", out) + } + for _, unit := range out.WorkUnits { + if unit.State != outcomes.WorkCompleted || unit.CorrectionsApplied != 0 { + t.Fatalf("no-edit section was not completed: %+v", unit) + } + } if len(seenBySection) != 3 { t.Fatalf("expected 3 chunked proposal calls, got %d", len(seenBySection)) }