2sagaco48 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/diagnostics.mjs +141 -0
- package/dist/compute.worker-j5ahIpF-.js +1 -0
- package/dist/graph.worker-DG6iGCB9.js +1 -0
- package/dist/index.html +2314 -0
- package/flatten-guide.md +248 -0
- package/index.html +12 -0
- package/package.json +38 -0
- package/src/App.orig.tsx +204 -0
- package/src/App.orig1.tsx +27 -0
- package/src/App.orig2.tsx +25 -0
- package/src/App.tsx +3 -0
- package/src/BaseApp.tsx +18 -0
- package/src/INTEGRATION_LEDGER.md +22 -0
- package/src/PERSIST_CANARY.orig.txt +1 -0
- package/src/PERSIST_CANARY.txt +1 -0
- package/src/REGRESSION_LEDGER.md +651 -0
- package/src/TEMPLATE_PIPELINE_AUDIT.md +208 -0
- package/src/WILLIAMS_PERSONA_GUIDE.md +62 -0
- package/src/chat/VeritasChat.tsx +69 -0
- package/src/chat/synthesis.ts +31 -0
- package/src/chat/tier.ts +45 -0
- package/src/components/AdaptersPage.orig.tsx +99 -0
- package/src/components/AdaptersPage.tsx +3 -0
- package/src/components/AdversarialPanel.tsx +136 -0
- package/src/components/AnchorBaselinePanel.tsx +75 -0
- package/src/components/AnswerPanel.tsx +60 -0
- package/src/components/ArtifactPanel.tsx +43 -0
- package/src/components/ChatApp.orig.tsx +1582 -0
- package/src/components/ChatApp.orig1.tsx +722 -0
- package/src/components/ChatApp.tsx +95 -0
- package/src/components/ChatAugmentPanels.tsx +73 -0
- package/src/components/CitationLedgerPanel.tsx +313 -0
- package/src/components/ComputeSandboxPanel.tsx +130 -0
- package/src/components/ConfigPanel.tsx +110 -0
- package/src/components/ControlPlanePage.tsx +195 -0
- package/src/components/CreativeTreeLifePage.tsx +234 -0
- package/src/components/CreativeTreeOfLifePanel.tsx +149 -0
- package/src/components/DebugTracePanel.tsx +137 -0
- package/src/components/DeepReasoningTrace.tsx +48 -0
- package/src/components/EntitySheetPanel.tsx +51 -0
- package/src/components/FailureModesPanel.tsx +104 -0
- package/src/components/GBSDashboard.tsx +239 -0
- package/src/components/GraphView.tsx +116 -0
- package/src/components/HUD.tsx +125 -0
- package/src/components/HypothesisPanel.tsx +67 -0
- package/src/components/InnovationGenomeEngine.tsx +792 -0
- package/src/components/InnovationPersonaGuide.tsx +348 -0
- package/src/components/InnovationPersonaPanel.tsx +289 -0
- package/src/components/LiveResourceHUD.tsx +109 -0
- package/src/components/LongWriterPanel.tsx +112 -0
- package/src/components/MainPipelineV10Bridge.tsx +72 -0
- package/src/components/MarkdownLite.tsx +63 -0
- package/src/components/MemoryInspector.tsx +38 -0
- package/src/components/MemoryMonitor.tsx +52 -0
- package/src/components/MemoryStressPanel.tsx +176 -0
- package/src/components/ModulesPage.tsx +171 -0
- package/src/components/PipelineDebugConsole.orig.tsx +1856 -0
- package/src/components/PipelineDebugConsole.tsx +3 -0
- package/src/components/PreFlightHUD.tsx +67 -0
- package/src/components/PrismaFetchTracePanel.tsx +87 -0
- package/src/components/ReportOSPanel.tsx +165 -0
- package/src/components/ResourceEstimatorPage.tsx +327 -0
- package/src/components/RichText.tsx +181 -0
- package/src/components/SagaOsPanel.tsx +605 -0
- package/src/components/SagaOsReference.tsx +421 -0
- package/src/components/SagaOsV2Panel.tsx +594 -0
- package/src/components/SagaStyleTab.tsx +382 -0
- package/src/components/SageBlueprintPanel.tsx +416 -0
- package/src/components/SharedChatInput.tsx +77 -0
- package/src/components/StatFinancePanel.tsx +190 -0
- package/src/components/StylePersonaPanel.tsx +128 -0
- package/src/components/TemplatesPage.tsx +300 -0
- package/src/components/TestPanel.tsx +63 -0
- package/src/components/TraceLog.tsx +89 -0
- package/src/components/V15CalibrationAugment.tsx +1134 -0
- package/src/components/V15CalibrationDialog.tsx +1871 -0
- package/src/components/V15Overlay.orig.tsx +120 -0
- package/src/components/V15Overlay.tsx +4 -0
- package/src/components/V15OverlayWrapper.tsx +362 -0
- package/src/components/V15Toggle.tsx +34 -0
- package/src/console/DiagnosticRecoveryPanel.tsx +358 -0
- package/src/console/EngineeringConsole.tsx +103 -0
- package/src/console/FictionSystemPanels.tsx +882 -0
- package/src/console/InspectorPanels.tsx +254 -0
- package/src/console/ManuscriptGraderPanel.tsx +182 -0
- package/src/console/RecursiveReviserPanel.tsx +245 -0
- package/src/console/SeedControlPanel.tsx +135 -0
- package/src/console/StoryControllerPanel.tsx +443 -0
- package/src/console/engineBridge.ts +258 -0
- package/src/console/gemini.ts +106 -0
- package/src/console/grading.ts +250 -0
- package/src/console/util.ts +45 -0
- package/src/fiction/continuity.ts +372 -0
- package/src/fiction/crash-protection.ts +111 -0
- package/src/fiction/diagnostics.ts +208 -0
- package/src/fiction/diff.ts +74 -0
- package/src/fiction/entity-state-machine.ts +214 -0
- package/src/fiction/nive-engine.ts +404 -0
- package/src/fiction/pipeline-selftest.ts +132 -0
- package/src/fiction/seed-control.ts +193 -0
- package/src/fiction/story-engine.ts +326 -0
- package/src/fiction/types.ts +68 -0
- package/src/flatten-guide.md +379 -0
- package/src/index.css +14 -0
- package/src/index.orig.css +13 -0
- package/src/lib/academic-sources.base.ts +188 -0
- package/src/lib/academic-sources.orig.ts +28 -0
- package/src/lib/academic-sources.ts +10 -0
- package/src/lib/advanced-math.ts +119 -0
- package/src/lib/adversarial-engine.base.ts +409 -0
- package/src/lib/adversarial-engine.ts +82 -0
- package/src/lib/ai-revision.ts +623 -0
- package/src/lib/app-state.tsx +426 -0
- package/src/lib/artifact-registry.ts +97 -0
- package/src/lib/artifacts.ts +180 -0
- package/src/lib/atlas-dr.ts +167 -0
- package/src/lib/browser-mtls.ts +127 -0
- package/src/lib/browser-queue.ts +221 -0
- package/src/lib/browser-search-scraper.base.ts +382 -0
- package/src/lib/browser-search-scraper.orig.ts +90 -0
- package/src/lib/browser-search-scraper.ts +10 -0
- package/src/lib/calc-interceptor.ts +82 -0
- package/src/lib/citation-lane-tap.ts +78 -0
- package/src/lib/citation-ledger-store.ts +369 -0
- package/src/lib/citation-ledger.ts +203 -0
- package/src/lib/compute-sandbox.ts +215 -0
- package/src/lib/compute.worker.ts +153 -0
- package/src/lib/connectors/gemini.ts +120 -0
- package/src/lib/connectors/jina.ts +233 -0
- package/src/lib/connectors/marketdata.ts +51 -0
- package/src/lib/connectors/prismafetch.base.ts +175 -0
- package/src/lib/connectors/prismafetch.ts +1 -0
- package/src/lib/connectors/serpapi.ts +65 -0
- package/src/lib/connectors/wikidata.ts +44 -0
- package/src/lib/constraints.ts +331 -0
- package/src/lib/continuation-detector.base.ts +153 -0
- package/src/lib/continuation-detector.ts +48 -0
- package/src/lib/contradraft.ts +168 -0
- package/src/lib/cors-proxy.ts +120 -0
- package/src/lib/coverage.ts +81 -0
- package/src/lib/debug/architecture-prescription.ts +556 -0
- package/src/lib/debug/covea-repair.ts +543 -0
- package/src/lib/debug/helios-ground.ts +319 -0
- package/src/lib/debug/intent-decomposer.ts +281 -0
- package/src/lib/debug/intent-lattice.ts +349 -0
- package/src/lib/debug/pipeline-diagnosis.ts +1149 -0
- package/src/lib/debug/pipeline-trace-bus.ts +424 -0
- package/src/lib/debug/prompt-forge.ts +678 -0
- package/src/lib/debug/repair-sites.ts +513 -0
- package/src/lib/debug/research-phase.ts +266 -0
- package/src/lib/debug/retrieval-context.ts +76 -0
- package/src/lib/debug/retrieval-hardener.ts +142 -0
- package/src/lib/debug/scraper-debug-runner.ts +576 -0
- package/src/lib/debug/scraper-forensics.ts +418 -0
- package/src/lib/debug/scraper-lane-roadmap.ts +136 -0
- package/src/lib/debug/self-test.ts +415 -0
- package/src/lib/debug/step-attribution.ts +211 -0
- package/src/lib/debug/template-rubric.ts +301 -0
- package/src/lib/debug/unified-innovation.ts +126 -0
- package/src/lib/debug/veritas-hybrid-scraper.ts +335 -0
- package/src/lib/defense-registry.ts +104 -0
- package/src/lib/defenses.ts +162 -0
- package/src/lib/deterministic-citation-ledger.ts +174 -0
- package/src/lib/elo-registry.base.ts +117 -0
- package/src/lib/elo-registry.ts +1 -0
- package/src/lib/entity-resolver.ts +291 -0
- package/src/lib/failure-modes.ts +267 -0
- package/src/lib/feature-registry.ts +69 -0
- package/src/lib/flaw-registry.ts +143 -0
- package/src/lib/flaws/_template.ts +52 -0
- package/src/lib/flaws/builtins.ts +63 -0
- package/src/lib/flaws/finance.ts +169 -0
- package/src/lib/flaws/fixers.ts +10 -0
- package/src/lib/flaws/index.ts +32 -0
- package/src/lib/flaws/legal.ts +157 -0
- package/src/lib/flaws/medical.ts +188 -0
- package/src/lib/flaws/original-defenses-pack.ts +81 -0
- package/src/lib/flaws/sample-declarative-pack.json +38 -0
- package/src/lib/flaws/selftest.ts +201 -0
- package/src/lib/flaws/software-extended.ts +125 -0
- package/src/lib/flaws/software-rn-webgl.ts +182 -0
- package/src/lib/flaws/statistics-advanced.ts +46 -0
- package/src/lib/flaws/statistics.ts +61 -0
- package/src/lib/gbse/config.ts +20 -0
- package/src/lib/gbse/engine.ts +226 -0
- package/src/lib/gbse/graph.ts +118 -0
- package/src/lib/gbse/graph.worker.ts +21 -0
- package/src/lib/gbse/tests.ts +117 -0
- package/src/lib/gbse/tiers.ts +65 -0
- package/src/lib/gbse/types.ts +94 -0
- package/src/lib/innovation-genome-engine-v2.orig.ts +1001 -0
- package/src/lib/innovation-genome-engine-v2.ts +3 -0
- package/src/lib/innovation-genome-engine.orig.ts +330 -0
- package/src/lib/innovation-genome-engine.ts +3 -0
- package/src/lib/innovation-genome-v10.orig.ts +314 -0
- package/src/lib/innovation-genome-v10.ts +2 -0
- package/src/lib/innovation-genome-v3.orig.ts +2015 -0
- package/src/lib/innovation-genome-v3.ts +2 -0
- package/src/lib/innovation-genome-v4.orig.ts +1959 -0
- package/src/lib/innovation-genome-v4.ts +2 -0
- package/src/lib/innovation-genome-v5.orig.ts +1210 -0
- package/src/lib/innovation-genome-v5.ts +2 -0
- package/src/lib/innovation-genome-v7.orig.ts +2549 -0
- package/src/lib/innovation-genome-v7.ts +2 -0
- package/src/lib/innovation-genome-v8.orig.ts +1485 -0
- package/src/lib/innovation-genome-v8.ts +2 -0
- package/src/lib/innovation-genome-v9.orig.ts +643 -0
- package/src/lib/innovation-genome-v9.ts +2 -0
- package/src/lib/jina.base.ts +293 -0
- package/src/lib/jina.ts +1 -0
- package/src/lib/live-telemetry.ts +61 -0
- package/src/lib/longwriter.ts +84 -0
- package/src/lib/manuscript-grade-v2.ts +298 -0
- package/src/lib/memory-governor.ts +173 -0
- package/src/lib/memory-stress-tests.ts +248 -0
- package/src/lib/model-intelligence.ts +48 -0
- package/src/lib/model-rotator.ts +75 -0
- package/src/lib/models.orig.ts +513 -0
- package/src/lib/models.ts +151 -0
- package/src/lib/n-deep.base.ts +592 -0
- package/src/lib/n-deep.ts +51 -0
- package/src/lib/nih-simulator.ts +56 -0
- package/src/lib/nih-vulnerability-fixes.ts +460 -0
- package/src/lib/omega-templates.base.ts +294 -0
- package/src/lib/omega-templates.ts +225 -0
- package/src/lib/omni-nexus.ts +64 -0
- package/src/lib/oracle-adapters.ts +67 -0
- package/src/lib/oracle-registry.ts +163 -0
- package/src/lib/orchestrator.ts +357 -0
- package/src/lib/output-boundary.ts +235 -0
- package/src/lib/overrides/vite-native-chaos-harness.ts +154 -0
- package/src/lib/overrides/vite-native-contract-plane.ts +245 -0
- package/src/lib/overrides/vite-native-doctor-plane.ts +208 -0
- package/src/lib/overrides/vite-native-knowledge-store.ts +267 -0
- package/src/lib/overrides/vite-native-policy-plane.ts +190 -0
- package/src/lib/overrides/vite-native-replay-plane.ts +197 -0
- package/src/lib/overrides/vite-native-runtime-plane.ts +106 -0
- package/src/lib/overrides/vite-native-scraper.ts +848 -0
- package/src/lib/overrides/vite-native-selftest-functional.ts +217 -0
- package/src/lib/overrides/vite-native-selftest.ts +183 -0
- package/src/lib/overrides/vite-native-snapshot-plane.ts +123 -0
- package/src/lib/philosophy-toolkit.ts +75 -0
- package/src/lib/pipeline.orig.ts +659 -0
- package/src/lib/pipeline.ts +42 -0
- package/src/lib/precache.ts +128 -0
- package/src/lib/py-sandbox.ts +205 -0
- package/src/lib/quality-score.ts +96 -0
- package/src/lib/quant-engine.ts +231 -0
- package/src/lib/quant-lib.ts +80 -0
- package/src/lib/reportos.ts +175 -0
- package/src/lib/research-os.ts +306 -0
- package/src/lib/resource-estimator.ts +114 -0
- package/src/lib/rpm-governor.ts +93 -0
- package/src/lib/runtime-estimator.ts +135 -0
- package/src/lib/sage/empty-space-v6.ts +560 -0
- package/src/lib/sage/empty-space-v7.ts +222 -0
- package/src/lib/sage/empty-space.ts +733 -0
- package/src/lib/sage/engine.ts +786 -0
- package/src/lib/sage/index.ts +17 -0
- package/src/lib/sage/judge.ts +246 -0
- package/src/lib/sage/prng.ts +53 -0
- package/src/lib/sage/reality-grounding.ts +380 -0
- package/src/lib/sage/repetition.ts +151 -0
- package/src/lib/sage/saga-os-v2-engines.ts +1870 -0
- package/src/lib/sage/saga-os-v2.ts +274 -0
- package/src/lib/sage/saga-os-v3-core.ts +1491 -0
- package/src/lib/sage/saga-os-v3-release.ts +739 -0
- package/src/lib/sage/saga-os-v4-aureate.ts +1161 -0
- package/src/lib/sage/saga-os-v4.ts +212 -0
- package/src/lib/sage/saga-os-v5.ts +205 -0
- package/src/lib/sage/saga-os-v6.ts +346 -0
- package/src/lib/sage/saga-os-v7.ts +314 -0
- package/src/lib/sage/saga-os-v8.ts +505 -0
- package/src/lib/sage/saga-os.ts +329 -0
- package/src/lib/sage/taxonomy.ts +403 -0
- package/src/lib/sage/unified-roll.ts +524 -0
- package/src/lib/sage/worked-sketches.ts +44 -0
- package/src/lib/sage.ts +166 -0
- package/src/lib/scraper-debug-bus.ts +53 -0
- package/src/lib/scraper-enhanced.orig.ts +153 -0
- package/src/lib/scraper-enhanced.ts +10 -0
- package/src/lib/scraper-hardener.base.orig.ts +155 -0
- package/src/lib/scraper-hardener.base.ts +317 -0
- package/src/lib/scraper-hardener.ts +1 -0
- package/src/lib/scraper-palisade/palisade-adjudicator.orig.ts +419 -0
- package/src/lib/scraper-palisade/palisade-adjudicator.ts +13 -0
- package/src/lib/scraper-vnext/arbiter-omega.orig.ts +688 -0
- package/src/lib/scraper-vnext/arbiter-omega.ts +13 -0
- package/src/lib/scraper-vnext/canonical-portfolio-augments.ts +479 -0
- package/src/lib/scraper-vnext/canonical-portfolio-orchestrator.orig.ts +1529 -0
- package/src/lib/scraper-vnext/canonical-portfolio-orchestrator.ts +13 -0
- package/src/lib/scraper-vnext/conclave-omega.ts +1830 -0
- package/src/lib/scraper-vnext/content-extractor-v2.ts +226 -0
- package/src/lib/scraper-vnext/diagnostics-suite.ts +36 -0
- package/src/lib/scraper-vnext/epistemic-packer.ts +97 -0
- package/src/lib/scraper-vnext/fusion-v2.ts +65 -0
- package/src/lib/scraper-vnext/hydra-reader.orig.ts +1216 -0
- package/src/lib/scraper-vnext/hydra-reader.ts +13 -0
- package/src/lib/scraper-vnext/native-scraper-browser-vnext.orig.ts +91 -0
- package/src/lib/scraper-vnext/native-scraper-browser-vnext.ts +13 -0
- package/src/lib/scraper-vnext/nexus-consensus.orig.ts +905 -0
- package/src/lib/scraper-vnext/nexus-consensus.ts +13 -0
- package/src/lib/scraper-vnext/portfolio-consensus-adjudicator.ts +804 -0
- package/src/lib/scraper-vnext/portfolio-consensus-memory.ts +377 -0
- package/src/lib/scraper-vnext/portfolio-terminal-governor.ts +548 -0
- package/src/lib/scraper-vnext/query-strategist.orig.ts +224 -0
- package/src/lib/scraper-vnext/query-strategist.ts +423 -0
- package/src/lib/scraper-vnext/retrieval-accelerator.ts +708 -0
- package/src/lib/scraper-vnext/retrieval-audit-augments.ts +477 -0
- package/src/lib/scraper-vnext/retrieval-control-plane.ts +904 -0
- package/src/lib/scraper-vnext/retrieval-policy-augments.ts +358 -0
- package/src/lib/scraper-vnext/safe-fetch-v2.ts +157 -0
- package/src/lib/scraper-vnext/sentinel-omega.ts +56 -0
- package/src/lib/scraper-vnext/sentinel-orchestrator.ts +69 -0
- package/src/lib/scraper-vnext/sibyl-oracle.orig.ts +413 -0
- package/src/lib/scraper-vnext/sibyl-oracle.ts +13 -0
- package/src/lib/scraper-vnext/smart-read-v2.ts +55 -0
- package/src/lib/scraper-vnext/spa-rescue-bridge.ts +608 -0
- package/src/lib/scraper-vnext/strata-engine.orig.ts +2666 -0
- package/src/lib/scraper-vnext/strata-engine.ts +13 -0
- package/src/lib/scraper-vnext/structured-source-adapter.orig.ts +369 -0
- package/src/lib/scraper-vnext/structured-source-adapter.ts +34 -0
- package/src/lib/scraper-vnext/terminal-complete.ts +224 -0
- package/src/lib/scraper-vnext/terminal-final.ts +277 -0
- package/src/lib/scraper-vnext/terminal-saturation.ts +345 -0
- package/src/lib/scraper-vnext/terminal-wire.ts +425 -0
- package/src/lib/scraper-vnext/vanguard-titanium.orig.ts +467 -0
- package/src/lib/scraper-vnext/vanguard-titanium.ts +17 -0
- package/src/lib/search-cache.ts +138 -0
- package/src/lib/sloop-runner.base.ts +223 -0
- package/src/lib/sloop-runner.ts +104 -0
- package/src/lib/sloop.ts +114 -0
- package/src/lib/small-model-adapters.ts +82 -0
- package/src/lib/sscp.ts +86 -0
- package/src/lib/system-map.ts +54 -0
- package/src/lib/uni-node.ts +86 -0
- package/src/lib/universal-rigor-guard.ts +433 -0
- package/src/lib/v15-gate-testbed.ts +274 -0
- package/src/lib/v15-gemini.base.ts +89 -0
- package/src/lib/v15-gemini.ts +1 -0
- package/src/lib/v15-grounding.orig.ts +241 -0
- package/src/lib/v15-grounding.ts +79 -0
- package/src/lib/v15-pipeline.base.ts +769 -0
- package/src/lib/v15-pipeline.orig.ts +1154 -0
- package/src/lib/v15-pipeline.ts +674 -0
- package/src/lib/v15-questions.ts +64 -0
- package/src/lib/v15-rate-limiter.orig.ts +91 -0
- package/src/lib/v15-rate-limiter.ts +48 -0
- package/src/lib/v15-state.base.ts +87 -0
- package/src/lib/v15-state.ts +28 -0
- package/src/lib/visual-table-generator.tsx +207 -0
- package/src/lib/wasm-runtime.ts +133 -0
- package/src/lib/williams-persona-guide.ts +265 -0
- package/src/lib/williams-style.base.ts +464 -0
- package/src/lib/williams-style.ts +59 -0
- package/src/lib/worker-pool.ts +96 -0
- package/src/lib/writing-tiers.ts +125 -0
- package/src/main.orig.tsx +10 -0
- package/src/main.tsx +10 -0
- package/src/utils/cn.orig.ts +6 -0
- package/src/utils/cn.ts +6 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +31 -0
- package/unify.mjs +537 -0
- package/vite.config.ts +18 -0
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* covea-repair.ts — NET-NEW (Type C). See flatten-guide.md.
|
|
3
|
+
* ===========================================================================
|
|
4
|
+
* TARGETED CoVe + Adversarial repair pass ("COVEA").
|
|
5
|
+
*
|
|
6
|
+
* OBSERVED FAILURE (from user's turn-3 trace, run-mse2o76t-8uaol):
|
|
7
|
+
* · CoVe found 4/4 claims inconsistent → NOTHING HAPPENED
|
|
8
|
+
* · Adversarial preflight found 4 blocking defects → injected as depth-N
|
|
9
|
+
* constraints, but depth-1 patch was rejected and the loop stopped
|
|
10
|
+
* · Adversarial red-team found 4 defects, monotonic repair "failed or
|
|
11
|
+
* shrank too much" → pre-repair text kept, defects logged, done
|
|
12
|
+
* · Guard score never moved from 4.4
|
|
13
|
+
*
|
|
14
|
+
* ARCHITECTURAL FIX (this file):
|
|
15
|
+
* Run a POST-completion COVEA pass. For each still-live CoVe inconsistency
|
|
16
|
+
* and each still-live adversarial defect, locate the EXACT sentence(s) that
|
|
17
|
+
* trigger the failure, then rewrite ONLY those sentences with a minimal,
|
|
18
|
+
* targeted prompt. NEVER regenerate the whole draft — that is what the
|
|
19
|
+
* in-package adversarial repair does, and its "shrank too much" rejection
|
|
20
|
+
* is exactly the failure mode we are avoiding.
|
|
21
|
+
*
|
|
22
|
+
* CONTRACT (hard, enforced):
|
|
23
|
+
* 1. A patch touches ≤ ONE paragraph at a time.
|
|
24
|
+
* 2. Total edited region ≤ 20% of the draft.
|
|
25
|
+
* 3. A patch that shrinks its region by > 40% is rejected — that is the
|
|
26
|
+
* "shrank too much" bug repeated at sentence scale.
|
|
27
|
+
* 4. Every citation tag [S#] present in the original region is preserved.
|
|
28
|
+
* 5. On model failure (429/503/parse), the sentence keeps its original text
|
|
29
|
+
* and is logged as `unrepaired` — never silently dropped.
|
|
30
|
+
* 6. NO PROMPT TRUNCATION. The full failing claim + full source snippets +
|
|
31
|
+
* full region text are passed verbatim.
|
|
32
|
+
* ===========================================================================
|
|
33
|
+
*/
|
|
34
|
+
import { geminiGenerate } from '@/lib/v15-gemini';
|
|
35
|
+
import { splitSentences } from "@/lib/debug/step-attribution";
|
|
36
|
+
|
|
37
|
+
export interface CoveFailure {
|
|
38
|
+
question: string;
|
|
39
|
+
expectedAnswer: string;
|
|
40
|
+
verifiedAnswer: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface AdversarialDefect {
|
|
44
|
+
code: string;
|
|
45
|
+
severity: string;
|
|
46
|
+
message: string;
|
|
47
|
+
category?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface CoveaTarget {
|
|
51
|
+
kind: "cove" | "adversarial";
|
|
52
|
+
reason: string;
|
|
53
|
+
/** Zero-based sentence indices this target maps onto in the current draft. */
|
|
54
|
+
sentenceIndices: number[];
|
|
55
|
+
/** The full sentence text at those indices, verbatim. */
|
|
56
|
+
sentences: string[];
|
|
57
|
+
/** For CoVe: the failing claim. For adversarial: the defect message. */
|
|
58
|
+
payload: string;
|
|
59
|
+
/** Suggested remediation extracted from the defect or verification answer. */
|
|
60
|
+
remediation: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface CoveaPatch {
|
|
64
|
+
target: CoveaTarget;
|
|
65
|
+
beforeText: string;
|
|
66
|
+
afterText: string;
|
|
67
|
+
accepted: boolean;
|
|
68
|
+
rejectReason?: string;
|
|
69
|
+
model: string;
|
|
70
|
+
latencyMs: number;
|
|
71
|
+
attempts: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface CoveaResult {
|
|
75
|
+
ranAt: number;
|
|
76
|
+
targets: CoveaTarget[];
|
|
77
|
+
patches: CoveaPatch[];
|
|
78
|
+
originalDraft: string;
|
|
79
|
+
repairedDraft: string;
|
|
80
|
+
charsChanged: number;
|
|
81
|
+
charsChangedPct: number;
|
|
82
|
+
acceptedCount: number;
|
|
83
|
+
rejectedCount: number;
|
|
84
|
+
skippedCount: number;
|
|
85
|
+
/** Diagnostic log lines — surfaced verbatim in the debug console. */
|
|
86
|
+
log: string[];
|
|
87
|
+
errors: string[];
|
|
88
|
+
/** Non-LLM repairs/containments applied before targeted patching. */
|
|
89
|
+
deterministicActions: string[];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface CitationAuditLike {
|
|
93
|
+
entries?: Array<{ id?: number; title?: string; url?: string; snippet?: string; content?: string; stage?: string }>;
|
|
94
|
+
auditResults?: Array<{ tag?: string; id?: number; found?: boolean; trusted?: boolean; snippetOverlap?: number; entailment?: number }>;
|
|
95
|
+
missingCount?: number;
|
|
96
|
+
untrustedCount?: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ── Target identification: locate the failing sentence in the draft ────────
|
|
100
|
+
|
|
101
|
+
function normalise(s: string): string {
|
|
102
|
+
return s.toLowerCase().replace(/\s+/g, " ").replace(/[^a-z0-9\s]/g, "");
|
|
103
|
+
}
|
|
104
|
+
function tokens(s: string): Set<string> {
|
|
105
|
+
return new Set(normalise(s).split(" ").filter((t) => t.length > 2));
|
|
106
|
+
}
|
|
107
|
+
function jaccard(a: Set<string>, b: Set<string>): number {
|
|
108
|
+
if (!a.size || !b.size) return 0;
|
|
109
|
+
let inter = 0;
|
|
110
|
+
for (const t of a) if (b.has(t)) inter++;
|
|
111
|
+
return inter / (a.size + b.size - inter);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Find the top-K sentences that overlap most with `needle`. */
|
|
115
|
+
function locateSentences(draft: string, needle: string, k = 2, minOverlap = 0.18): number[] {
|
|
116
|
+
const sents = splitSentences(draft);
|
|
117
|
+
const nToks = tokens(needle);
|
|
118
|
+
if (!nToks.size) return [];
|
|
119
|
+
const scored = sents
|
|
120
|
+
.map((s, i) => ({ i, score: jaccard(tokens(s), nToks) }))
|
|
121
|
+
.filter((x) => x.score >= minOverlap)
|
|
122
|
+
.sort((a, b) => b.score - a.score)
|
|
123
|
+
.slice(0, k)
|
|
124
|
+
.map((x) => x.i);
|
|
125
|
+
return scored;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function buildTargets(
|
|
129
|
+
draft: string,
|
|
130
|
+
coveFailures: CoveFailure[],
|
|
131
|
+
advDefects: AdversarialDefect[]
|
|
132
|
+
): CoveaTarget[] {
|
|
133
|
+
const sents = splitSentences(draft);
|
|
134
|
+
const targets: CoveaTarget[] = [];
|
|
135
|
+
|
|
136
|
+
for (const f of coveFailures) {
|
|
137
|
+
// CoVe questions come with the CLAIM the draft made ("expectedAnswer") and
|
|
138
|
+
// what verification found. The claim is what actually lives in the draft.
|
|
139
|
+
const indices = locateSentences(draft, `${f.question} ${f.expectedAnswer}`, 2, 0.15);
|
|
140
|
+
if (indices.length === 0) continue;
|
|
141
|
+
targets.push({
|
|
142
|
+
kind: "cove",
|
|
143
|
+
reason: `CoVe failed: "${f.question}"`,
|
|
144
|
+
sentenceIndices: indices,
|
|
145
|
+
sentences: indices.map((i) => sents[i]),
|
|
146
|
+
payload: f.expectedAnswer,
|
|
147
|
+
remediation:
|
|
148
|
+
`Re-verify against evidence. The current sentence(s) claim: "${f.expectedAnswer}". ` +
|
|
149
|
+
`Independent verification returned: "${f.verifiedAnswer}". ` +
|
|
150
|
+
`Either narrow the claim to what the evidence supports, tag it [UNVERIFIED], or remove it.`,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for (const d of advDefects) {
|
|
155
|
+
// The defect message often names a phrase or gate; use it as the needle.
|
|
156
|
+
const indices = locateSentences(draft, d.message, 2, 0.12);
|
|
157
|
+
if (indices.length === 0) {
|
|
158
|
+
// Structural defect with no obvious anchor — apply as document-level hint.
|
|
159
|
+
// Recorded as an EMPTY-index target so the aggregator can still log it.
|
|
160
|
+
targets.push({
|
|
161
|
+
kind: "adversarial",
|
|
162
|
+
reason: `Adversarial defect (no sentence anchor): ${d.code}`,
|
|
163
|
+
sentenceIndices: [],
|
|
164
|
+
sentences: [],
|
|
165
|
+
payload: d.message,
|
|
166
|
+
remediation: d.message,
|
|
167
|
+
});
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
targets.push({
|
|
171
|
+
kind: "adversarial",
|
|
172
|
+
reason: `Adversarial defect: ${d.code} [${d.severity}]`,
|
|
173
|
+
sentenceIndices: indices,
|
|
174
|
+
sentences: indices.map((i) => sents[i]),
|
|
175
|
+
payload: d.message,
|
|
176
|
+
remediation: d.message,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return targets;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ── Per-target LLM rewrite (minimal, targeted, resilient) ──────────────────
|
|
183
|
+
|
|
184
|
+
const CITATION_RE = /\[[Ss]?\d{1,3}\]/g;
|
|
185
|
+
const CITATION_GROUP_RE = /\[S?\d{1,3}(?:\s*,\s*S?\d{1,3})+\]/gi;
|
|
186
|
+
const SINGLE_CITATION_RE = /\[S?(\d{1,3})\]/gi;
|
|
187
|
+
const PLACEHOLDER_MARKER_RE = /\[(DATA GAP|ASSUMPTION|TBD|PROPOSED)\]/gi;
|
|
188
|
+
|
|
189
|
+
function isRealUrl(url: unknown): boolean {
|
|
190
|
+
return typeof url === "string" && /^https?:\/\//i.test(url.trim());
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function isCssResetSnippet(text: unknown): boolean {
|
|
194
|
+
const s = String(text ?? "").toLowerCase();
|
|
195
|
+
return /table,\s*caption,\s*tbody/.test(s) || /--font-inter|font-family:\s*var\(--font/.test(s);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function sourceWordOverlap(question: string, sourceText: string): number {
|
|
199
|
+
const stop = new Set(["that", "this", "with", "from", "have", "what", "your", "will", "into", "using", "made", "large", "demand", "product", "exist", "doesn", "doesnt"]);
|
|
200
|
+
const q = new Set((question.toLowerCase().match(/[a-z0-9]{4,}/g) ?? []).filter((w) => !stop.has(w)));
|
|
201
|
+
if (q.size === 0) return 0;
|
|
202
|
+
const hay = sourceText.toLowerCase();
|
|
203
|
+
let hit = 0;
|
|
204
|
+
for (const w of q) if (hay.includes(w)) hit++;
|
|
205
|
+
return hit / q.size;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function invalidCitationIds(question: string, audit?: CitationAuditLike): Set<number> {
|
|
209
|
+
const bad = new Set<number>();
|
|
210
|
+
const entries = audit?.entries ?? [];
|
|
211
|
+
for (const entry of entries) {
|
|
212
|
+
const id = Number(entry.id);
|
|
213
|
+
if (!Number.isFinite(id)) continue;
|
|
214
|
+
const text = `${entry.title ?? ""} ${entry.snippet ?? entry.content ?? ""}`;
|
|
215
|
+
if (!isRealUrl(entry.url) || isCssResetSnippet(text) || sourceWordOverlap(question, text) === 0) bad.add(id);
|
|
216
|
+
}
|
|
217
|
+
for (const r of audit?.auditResults ?? []) {
|
|
218
|
+
const id = Number(r.id ?? String(r.tag ?? "").match(/\d+/)?.[0]);
|
|
219
|
+
if (!Number.isFinite(id)) continue;
|
|
220
|
+
if (r.found === false || r.trusted === false || Number(r.snippetOverlap ?? 1) < 0.2 || Number(r.entailment ?? 1) === 0) bad.add(id);
|
|
221
|
+
}
|
|
222
|
+
return bad;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function stripInvalidCitationTags(text: string, badIds: Set<number>): { text: string; actions: string[] } {
|
|
226
|
+
if (badIds.size === 0) return { text, actions: [] };
|
|
227
|
+
const actions: string[] = [];
|
|
228
|
+
let next = text.replace(CITATION_GROUP_RE, (group) => {
|
|
229
|
+
const ids = Array.from(group.matchAll(/S?(\d{1,3})/gi)).map((m) => Number(m[1]));
|
|
230
|
+
const kept = ids.filter((id) => !badIds.has(id));
|
|
231
|
+
const removed = ids.filter((id) => badIds.has(id));
|
|
232
|
+
if (removed.length) actions.push(`Removed invalid grouped citation id(s) ${removed.map((id) => `[S${id}]`).join(", ")}.`);
|
|
233
|
+
return kept.length ? `[${kept.map((id) => `S${id}`).join(", ")}]` : "";
|
|
234
|
+
});
|
|
235
|
+
next = next.replace(SINGLE_CITATION_RE, (tag, raw) => {
|
|
236
|
+
const id = Number(raw);
|
|
237
|
+
if (!badIds.has(id)) return tag;
|
|
238
|
+
actions.push(`Removed invalid citation [S${id}].`);
|
|
239
|
+
return "";
|
|
240
|
+
});
|
|
241
|
+
return { text: next.replace(/\s+([.,;:])/g, "$1").replace(/[ \t]{2,}/g, " "), actions };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function normalizeBarePlaceholders(text: string): { text: string; actions: string[] } {
|
|
245
|
+
const actions: string[] = [];
|
|
246
|
+
const next = text.replace(PLACEHOLDER_MARKER_RE, (_m, kind) => {
|
|
247
|
+
const label = String(kind).toUpperCase();
|
|
248
|
+
actions.push(`Converted bare [${label}] marker to explicit COVEA open-item marker.`);
|
|
249
|
+
return label === "DATA GAP" || label === "TBD"
|
|
250
|
+
? "[OPEN_INPUT: owner, source, formula required before decision sign-off]"
|
|
251
|
+
: "[EXPLICIT_ASSUMPTION: owner, formula, sensitivity required before 10/10 sign-off]";
|
|
252
|
+
});
|
|
253
|
+
return { text: next, actions };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function synthesizeReferences(text: string, audit?: CitationAuditLike): { text: string; actions: string[] } {
|
|
257
|
+
const entries = (audit?.entries ?? []).filter((e) => Number.isFinite(Number(e.id)) && isRealUrl(e.url));
|
|
258
|
+
if (entries.length === 0) return { text, actions: [] };
|
|
259
|
+
const refs = ["## References", ...entries.map((e) => `[S${Number(e.id)}] ${e.title || "Untitled"}. ${e.url}`)].join("\n");
|
|
260
|
+
if (/##\s*references\b/i.test(text)) return { text, actions: [] };
|
|
261
|
+
return { text: `${text.trim()}\n\n${refs}\n`, actions: [`Appended deterministic References section with ${entries.length} URL-backed source(s).`] };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function preservedCitations(text: string): string[] {
|
|
265
|
+
return Array.from(text.matchAll(CITATION_RE)).map((m) => m[0]);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function rewriteRegion(opts: {
|
|
269
|
+
apiKey: string;
|
|
270
|
+
model: string;
|
|
271
|
+
question: string;
|
|
272
|
+
region: string;
|
|
273
|
+
target: CoveaTarget;
|
|
274
|
+
innovationContext?: string;
|
|
275
|
+
researchEvidence?: string;
|
|
276
|
+
}): Promise<{ text: string; ok: boolean; error?: string }> {
|
|
277
|
+
const cites = preservedCitations(opts.region);
|
|
278
|
+
// NO TRUNCATION. Full region + full remediation + full payload.
|
|
279
|
+
const innovationBlock = opts.innovationContext ? `\nINNOVATION PERSONA/PATH (stay in voice; do not contradict the discovery framing):\n${opts.innovationContext.slice(0, 600)}\n` : "";
|
|
280
|
+
const evidenceBlock = opts.researchEvidence ? `\nAVAILABLE EVIDENCE (cite from this if correcting a factual claim; never invent):\n${opts.researchEvidence.slice(0, 2000)}\n` : "";
|
|
281
|
+
const prompt = `You are a targeted copy-editor. You will rewrite ONLY the passage below, minimally, to satisfy exactly one specific defect. Do not add new content. Do not rewrite anything not in the passage. Preserve every citation marker [S#] verbatim (${cites.join(", ") || "none present"}).
|
|
282
|
+
|
|
283
|
+
USER QUESTION (for context only, do not restate):
|
|
284
|
+
${opts.question}
|
|
285
|
+
${innovationBlock}${evidenceBlock}
|
|
286
|
+
DEFECT TO RESOLVE (${opts.target.kind.toUpperCase()}):
|
|
287
|
+
${opts.target.remediation}
|
|
288
|
+
|
|
289
|
+
PASSAGE TO REPAIR (do not exceed the length of this passage by more than 20%):
|
|
290
|
+
<<<
|
|
291
|
+
${opts.region}
|
|
292
|
+
>>>
|
|
293
|
+
|
|
294
|
+
Output the rewritten passage only. No preamble, no headers, no code fences, no commentary.`;
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
const res: any = await geminiGenerate({
|
|
298
|
+
apiKey: opts.apiKey,
|
|
299
|
+
model: opts.model,
|
|
300
|
+
prompt,
|
|
301
|
+
maxOutputTokens: Math.max(400, Math.min(2400, Math.ceil(opts.region.length / 2))),
|
|
302
|
+
});
|
|
303
|
+
const text: string = String(res?.text ?? res?.output ?? "").trim();
|
|
304
|
+
if (!text) return { text: "", ok: false, error: "empty model response" };
|
|
305
|
+
return { text, ok: true };
|
|
306
|
+
} catch (e) {
|
|
307
|
+
return { text: "", ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ── Backup route: deterministic tag-annotation when the LLM route fails ────
|
|
312
|
+
|
|
313
|
+
function deterministicPatch(region: string, target: CoveaTarget): string {
|
|
314
|
+
// Non-prompt architectural fallback: annotate the region with a machine
|
|
315
|
+
// readable tag so downstream renderers can highlight and human reviewers can
|
|
316
|
+
// fix. Never silently mutate content beyond adding a suffix tag.
|
|
317
|
+
const tag =
|
|
318
|
+
target.kind === "cove"
|
|
319
|
+
? "[COVEA:UNVERIFIED]"
|
|
320
|
+
: "[COVEA:ADV_DEFECT]";
|
|
321
|
+
if (region.trim().endsWith(tag)) return region; // idempotent
|
|
322
|
+
return `${region.trim()} ${tag}`;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ── Aggregate: build the repaired draft one target at a time ───────────────
|
|
326
|
+
|
|
327
|
+
const MAX_TOTAL_EDIT_PCT = 0.2; // hard cap: never touch >20% of the draft
|
|
328
|
+
const MAX_SHRINK_PCT = 0.4; // reject if the rewrite lost >40% of its region
|
|
329
|
+
|
|
330
|
+
const MODEL_PRIMARY = "gemini-2.5-flash";
|
|
331
|
+
const MODEL_FALLBACK = "gemini-2.0-flash-lite";
|
|
332
|
+
|
|
333
|
+
export async function runCoveaRepair(opts: {
|
|
334
|
+
apiKey: string;
|
|
335
|
+
question: string;
|
|
336
|
+
draft: string;
|
|
337
|
+
coveFailures: CoveFailure[];
|
|
338
|
+
advDefects: AdversarialDefect[];
|
|
339
|
+
citationAudit?: CitationAuditLike;
|
|
340
|
+
/** Innovation persona + path context so repairs stay in-voice. */
|
|
341
|
+
innovationContext?: string;
|
|
342
|
+
/** Prewriting research evidence block so repairs can cite real sources. */
|
|
343
|
+
researchEvidence?: string;
|
|
344
|
+
onProgress?: (msg: string) => void;
|
|
345
|
+
}): Promise<CoveaResult> {
|
|
346
|
+
const log: string[] = [];
|
|
347
|
+
const errors: string[] = [];
|
|
348
|
+
const deterministicActions: string[] = [];
|
|
349
|
+
const emit = (m: string) => {
|
|
350
|
+
log.push(m);
|
|
351
|
+
try {
|
|
352
|
+
opts.onProgress?.(`COVEA · ${m}`);
|
|
353
|
+
} catch {
|
|
354
|
+
/* fail-open */
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
let workingDraft = opts.draft;
|
|
359
|
+
|
|
360
|
+
// Deterministic containment runs even with no API key. It is deliberately
|
|
361
|
+
// non-generative: remove bad citation tags, make unresolved placeholders
|
|
362
|
+
// explicit, and append a ledger-derived References section.
|
|
363
|
+
const badIds = invalidCitationIds(opts.question, opts.citationAudit);
|
|
364
|
+
const citationFix = stripInvalidCitationTags(workingDraft, badIds);
|
|
365
|
+
if (citationFix.text !== workingDraft) {
|
|
366
|
+
workingDraft = citationFix.text;
|
|
367
|
+
deterministicActions.push(...citationFix.actions);
|
|
368
|
+
}
|
|
369
|
+
const placeholderFix = normalizeBarePlaceholders(workingDraft);
|
|
370
|
+
if (placeholderFix.text !== workingDraft) {
|
|
371
|
+
workingDraft = placeholderFix.text;
|
|
372
|
+
deterministicActions.push(...placeholderFix.actions);
|
|
373
|
+
}
|
|
374
|
+
const refsFix = synthesizeReferences(workingDraft, opts.citationAudit);
|
|
375
|
+
if (refsFix.text !== workingDraft) {
|
|
376
|
+
workingDraft = refsFix.text;
|
|
377
|
+
deterministicActions.push(...refsFix.actions);
|
|
378
|
+
}
|
|
379
|
+
for (const action of deterministicActions) emit(`deterministic · ${action}`);
|
|
380
|
+
|
|
381
|
+
const targets = buildTargets(workingDraft, opts.coveFailures, opts.advDefects);
|
|
382
|
+
emit(`identified ${targets.length} target(s) (${opts.coveFailures.length} CoVe, ${opts.advDefects.length} adversarial)`);
|
|
383
|
+
|
|
384
|
+
const patches: CoveaPatch[] = [];
|
|
385
|
+
let cumulativeEditedChars = 0;
|
|
386
|
+
let accepted = 0,
|
|
387
|
+
rejected = 0,
|
|
388
|
+
skipped = 0;
|
|
389
|
+
|
|
390
|
+
for (const target of targets) {
|
|
391
|
+
if (target.sentenceIndices.length === 0) {
|
|
392
|
+
emit(`skip · ${target.reason} · no sentence anchor found (document-level defect logged only)`);
|
|
393
|
+
patches.push({
|
|
394
|
+
target,
|
|
395
|
+
beforeText: "",
|
|
396
|
+
afterText: "",
|
|
397
|
+
accepted: false,
|
|
398
|
+
rejectReason: "no sentence anchor",
|
|
399
|
+
model: "n/a",
|
|
400
|
+
latencyMs: 0,
|
|
401
|
+
attempts: 0,
|
|
402
|
+
});
|
|
403
|
+
skipped++;
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Assemble the region: contiguous span from min to max sentence index.
|
|
408
|
+
const sents = splitSentences(workingDraft);
|
|
409
|
+
const min = Math.min(...target.sentenceIndices);
|
|
410
|
+
const max = Math.max(...target.sentenceIndices);
|
|
411
|
+
// Cap the region at ~one paragraph (≤5 contiguous sentences).
|
|
412
|
+
const regionStart = min;
|
|
413
|
+
const regionEnd = Math.min(sents.length - 1, Math.min(max, min + 4));
|
|
414
|
+
const region = sents.slice(regionStart, regionEnd + 1).join(" ");
|
|
415
|
+
|
|
416
|
+
if (region.length === 0) {
|
|
417
|
+
skipped++;
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Budget guard: total edited region ≤ 20% of the original draft length.
|
|
422
|
+
if (cumulativeEditedChars + region.length > opts.draft.length * MAX_TOTAL_EDIT_PCT) {
|
|
423
|
+
emit(`skip · budget · would exceed 20% total edit cap (already ${cumulativeEditedChars}/${opts.draft.length}ch, this region ${region.length}ch)`);
|
|
424
|
+
skipped++;
|
|
425
|
+
patches.push({
|
|
426
|
+
target,
|
|
427
|
+
beforeText: region,
|
|
428
|
+
afterText: region,
|
|
429
|
+
accepted: false,
|
|
430
|
+
rejectReason: "20% budget cap reached",
|
|
431
|
+
model: "n/a",
|
|
432
|
+
latencyMs: 0,
|
|
433
|
+
attempts: 0,
|
|
434
|
+
});
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const started = Date.now();
|
|
439
|
+
// Try primary model, then fallback, then deterministic annotation.
|
|
440
|
+
let attempt = 0;
|
|
441
|
+
let after = "";
|
|
442
|
+
let modelUsed = MODEL_PRIMARY;
|
|
443
|
+
let lastErr = "";
|
|
444
|
+
if (opts.apiKey) {
|
|
445
|
+
for (const model of [MODEL_PRIMARY, MODEL_FALLBACK]) {
|
|
446
|
+
attempt++;
|
|
447
|
+
modelUsed = model;
|
|
448
|
+
const res = await rewriteRegion({ apiKey: opts.apiKey, model, question: opts.question, region, target, innovationContext: opts.innovationContext, researchEvidence: opts.researchEvidence });
|
|
449
|
+
if (res.ok && res.text) {
|
|
450
|
+
after = res.text;
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
lastErr = res.error ?? "unknown";
|
|
454
|
+
emit(`retry · ${target.kind} · ${model} → ${lastErr}`);
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
lastErr = "no API key; skipped LLM route";
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (!after) {
|
|
461
|
+
// Backup route: deterministic annotation. Preserves all original content.
|
|
462
|
+
after = deterministicPatch(region, target);
|
|
463
|
+
modelUsed = "deterministic-annotation";
|
|
464
|
+
errors.push(`${target.reason} → LLM route unavailable (${lastErr}); applied deterministic annotation`);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Sanity gates on the rewrite.
|
|
468
|
+
const shrinkPct = (region.length - after.length) / region.length;
|
|
469
|
+
const preservedBefore = preservedCitations(region);
|
|
470
|
+
const preservedAfter = preservedCitations(after);
|
|
471
|
+
const missingCites = preservedBefore.filter((c) => !preservedAfter.includes(c));
|
|
472
|
+
let rejectReason: string | undefined;
|
|
473
|
+
if (shrinkPct > MAX_SHRINK_PCT) rejectReason = `shrank ${(shrinkPct * 100).toFixed(0)}% (>40% cap)`;
|
|
474
|
+
else if (missingCites.length > 0) rejectReason = `dropped citation(s) ${missingCites.join(", ")}`;
|
|
475
|
+
|
|
476
|
+
if (rejectReason) {
|
|
477
|
+
patches.push({
|
|
478
|
+
target,
|
|
479
|
+
beforeText: region,
|
|
480
|
+
afterText: after,
|
|
481
|
+
accepted: false,
|
|
482
|
+
rejectReason,
|
|
483
|
+
model: modelUsed,
|
|
484
|
+
latencyMs: Date.now() - started,
|
|
485
|
+
attempts: attempt,
|
|
486
|
+
});
|
|
487
|
+
emit(`reject · ${target.reason} · ${rejectReason} · kept original`);
|
|
488
|
+
rejected++;
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Apply the patch by string replacement of the exact region.
|
|
493
|
+
const idx = workingDraft.indexOf(region);
|
|
494
|
+
if (idx === -1) {
|
|
495
|
+
// Region no longer literally present (previous patch changed spacing).
|
|
496
|
+
// Fall back to leaving text alone.
|
|
497
|
+
patches.push({
|
|
498
|
+
target,
|
|
499
|
+
beforeText: region,
|
|
500
|
+
afterText: after,
|
|
501
|
+
accepted: false,
|
|
502
|
+
rejectReason: "region no longer present in working draft (order dependency)",
|
|
503
|
+
model: modelUsed,
|
|
504
|
+
latencyMs: Date.now() - started,
|
|
505
|
+
attempts: attempt,
|
|
506
|
+
});
|
|
507
|
+
rejected++;
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
workingDraft = workingDraft.slice(0, idx) + after + workingDraft.slice(idx + region.length);
|
|
511
|
+
cumulativeEditedChars += region.length;
|
|
512
|
+
patches.push({
|
|
513
|
+
target,
|
|
514
|
+
beforeText: region,
|
|
515
|
+
afterText: after,
|
|
516
|
+
accepted: true,
|
|
517
|
+
model: modelUsed,
|
|
518
|
+
latencyMs: Date.now() - started,
|
|
519
|
+
attempts: attempt,
|
|
520
|
+
});
|
|
521
|
+
emit(`accept · ${target.reason} · ${region.length}→${after.length}ch · ${modelUsed}`);
|
|
522
|
+
accepted++;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const charsChanged = Math.abs(workingDraft.length - opts.draft.length);
|
|
526
|
+
const pct = opts.draft.length ? charsChanged / opts.draft.length : 0;
|
|
527
|
+
|
|
528
|
+
return {
|
|
529
|
+
ranAt: Date.now(),
|
|
530
|
+
targets,
|
|
531
|
+
patches,
|
|
532
|
+
originalDraft: opts.draft,
|
|
533
|
+
repairedDraft: workingDraft,
|
|
534
|
+
charsChanged,
|
|
535
|
+
charsChangedPct: pct,
|
|
536
|
+
acceptedCount: accepted,
|
|
537
|
+
rejectedCount: rejected,
|
|
538
|
+
skippedCount: skipped,
|
|
539
|
+
log,
|
|
540
|
+
errors,
|
|
541
|
+
deterministicActions,
|
|
542
|
+
};
|
|
543
|
+
}
|