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,331 @@
|
|
|
1
|
+
// Deterministic constraint extraction and enforcement.
|
|
2
|
+
// Solves the "60-day blindspot" and "instruction spillage" class of failures.
|
|
3
|
+
//
|
|
4
|
+
// 1) extractConstraints() pulls explicit constraints (time horizon, format,
|
|
5
|
+
// comparison targets, scope hints) from the user's prompt using regex,
|
|
6
|
+
// so the LLM is bound to them rather than asked to infer them.
|
|
7
|
+
// 2) sanitizeOutput() strips leaked scratchpad/outline/constraint blocks
|
|
8
|
+
// from the model output BEFORE the user sees it.
|
|
9
|
+
// 3) buildConstraintBlock() converts extracted constraints into a hard
|
|
10
|
+
// instruction block that the synthesis prompt prepends.
|
|
11
|
+
|
|
12
|
+
export interface TimeHorizon {
|
|
13
|
+
value: number;
|
|
14
|
+
unit: "day" | "week" | "month" | "quarter" | "year";
|
|
15
|
+
rawMatch: string;
|
|
16
|
+
/** Approx days, for filtering decisions. */
|
|
17
|
+
days: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ExtractedConstraints {
|
|
21
|
+
timeHorizon?: TimeHorizon;
|
|
22
|
+
/** Tickers / proper nouns the user named (e.g., NVDA). Used to keep focus. */
|
|
23
|
+
namedEntities: string[];
|
|
24
|
+
/** Explicit comparison targets, e.g. "Is X the best?" → "X". */
|
|
25
|
+
explicitComparisonTargets: string[];
|
|
26
|
+
/** Format hints: "table", "list", "summary", "short", "long", "bullet". */
|
|
27
|
+
formatHints: string[];
|
|
28
|
+
/** Detected user-asked exclusions: "without X", "except X". */
|
|
29
|
+
exclusions: string[];
|
|
30
|
+
/** Single-sentence summary the constraint block restates back to the model. */
|
|
31
|
+
scopeSentence?: string;
|
|
32
|
+
/** Domain flag, used to attach domain-specific guardrails. */
|
|
33
|
+
domain?:
|
|
34
|
+
| "financial"
|
|
35
|
+
| "medical"
|
|
36
|
+
| "legal"
|
|
37
|
+
| "scientific"
|
|
38
|
+
| "engineering"
|
|
39
|
+
| "general";
|
|
40
|
+
/** True when the question is clearly tactical/short-horizon (≤ 90 days). */
|
|
41
|
+
isShortHorizon: boolean;
|
|
42
|
+
/** True when the question is clearly strategic/long-horizon (≥ 1 year). */
|
|
43
|
+
isLongHorizon: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const WORD_NUMBERS: Record<string, number> = {
|
|
47
|
+
a: 1, one: 1, two: 2, three: 3, four: 4, five: 5, six: 6,
|
|
48
|
+
seven: 7, eight: 8, nine: 9, ten: 10, eleven: 11, twelve: 12,
|
|
49
|
+
couple: 2, few: 3, several: 4, dozen: 12,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const UNIT_DAYS: Record<TimeHorizon["unit"], number> = {
|
|
53
|
+
day: 1, week: 7, month: 30, quarter: 91, year: 365,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const FINANCIAL_HINTS = /\b(stock|stocks|ticker|equity|equities|share|shares|invest|investment|portfolio|earnings|nvda|tsla|aapl|msft|spy|qqq|s&p|nasdaq|dividend|trade|trader|bull|bear|long|short|put|call|option|crypto|btc|eth)\b/i;
|
|
57
|
+
const MEDICAL_HINTS = /\b(symptom|diagnosis|disease|drug|dose|dosage|mg|patient|treatment|therapy|clinical|trial|medication|side effect)\b/i;
|
|
58
|
+
const LEGAL_HINTS = /\b(lawsuit|statute|jurisdiction|liability|contract clause|tort|plaintiff|defendant|court|ruling|precedent|jurisprudence)\b/i;
|
|
59
|
+
const SCIENCE_HINTS = /\b(study|hypothesis|p-?value|placebo|peer[-\s]?review|methodology|systematic review|meta[-\s]?analysis)\b/i;
|
|
60
|
+
const ENG_HINTS = /\b(architecture|algorithm|throughput|latency|protocol|specification|kubernetes|microservice|library|framework|api)\b/i;
|
|
61
|
+
|
|
62
|
+
function detectDomain(text: string): ExtractedConstraints["domain"] {
|
|
63
|
+
if (FINANCIAL_HINTS.test(text)) return "financial";
|
|
64
|
+
if (MEDICAL_HINTS.test(text)) return "medical";
|
|
65
|
+
if (LEGAL_HINTS.test(text)) return "legal";
|
|
66
|
+
if (SCIENCE_HINTS.test(text)) return "scientific";
|
|
67
|
+
if (ENG_HINTS.test(text)) return "engineering";
|
|
68
|
+
return "general";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const TIME_RE =
|
|
72
|
+
/\b(?:next|coming|upcoming|over\s+the\s+next|within|in\s+the\s+next|for\s+the\s+next|in)\s+(a|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|couple(?:\s+of)?|few|several|dozen|\d+)\s+(day|week|month|quarter|year)s?\b/i;
|
|
73
|
+
const TIME_BARE_RE = /\b(\d+)[-\s]?(day|week|month|quarter|year)s?\b/i;
|
|
74
|
+
|
|
75
|
+
function extractTimeHorizon(text: string): TimeHorizon | undefined {
|
|
76
|
+
const m = text.match(TIME_RE) || text.match(TIME_BARE_RE);
|
|
77
|
+
if (!m) return undefined;
|
|
78
|
+
const wordValue = m[1].toLowerCase().replace(/\s+of$/, "");
|
|
79
|
+
const value = WORD_NUMBERS[wordValue] ?? Number(wordValue);
|
|
80
|
+
if (!Number.isFinite(value) || value <= 0) return undefined;
|
|
81
|
+
const unit = m[2].toLowerCase() as TimeHorizon["unit"];
|
|
82
|
+
return { value, unit, rawMatch: m[0], days: value * UNIT_DAYS[unit] };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// "Is NVDA the best?" / "Is AAPL good?" / "Should I buy TSLA?"
|
|
86
|
+
const COMPARE_RE = /\b(?:is|are|should\s+i\s+(?:buy|sell|hold))\s+([A-Z][A-Z0-9.\-]{0,9})\b/g;
|
|
87
|
+
// Standalone tickers in caps (3-5 chars) anywhere in the text.
|
|
88
|
+
const TICKER_RE = /\b([A-Z]{2,5})\b/g;
|
|
89
|
+
const TICKER_STOPWORDS = new Set([
|
|
90
|
+
"I", "A", "AN", "AND", "OR", "BUT", "FOR", "NOT", "THE", "TO", "OF",
|
|
91
|
+
"IN", "ON", "AT", "BY", "AS", "IS", "BE", "DO", "GO", "GET",
|
|
92
|
+
"WHY", "HOW", "WHAT", "WHO", "WHEN", "WHERE", "WHICH", "MY", "ME", "WE",
|
|
93
|
+
"US", "YOU", "YOUR", "OUR", "HIS", "HER", "ITS", "IT", "SO", "IF",
|
|
94
|
+
"BEST", "WORST", "GOOD", "BAD", "OK", "OKAY", "VS", "VS.", "TLDR",
|
|
95
|
+
"ANY", "ALL", "NEW", "OLD", "TOP", "PRO", "CON", "AI", "ML", "API",
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
function extractTickers(text: string): { explicit: string[]; named: string[] } {
|
|
99
|
+
const explicit = new Set<string>();
|
|
100
|
+
const named = new Set<string>();
|
|
101
|
+
let m: RegExpExecArray | null;
|
|
102
|
+
while ((m = COMPARE_RE.exec(text)) !== null) {
|
|
103
|
+
const t = m[1];
|
|
104
|
+
if (!TICKER_STOPWORDS.has(t.toUpperCase())) explicit.add(t.toUpperCase());
|
|
105
|
+
}
|
|
106
|
+
while ((m = TICKER_RE.exec(text)) !== null) {
|
|
107
|
+
const t = m[1];
|
|
108
|
+
if (!TICKER_STOPWORDS.has(t)) named.add(t);
|
|
109
|
+
}
|
|
110
|
+
return { explicit: [...explicit], named: [...named] };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const FORMAT_TOKENS: Array<{ re: RegExp; tag: string }> = [
|
|
114
|
+
{ re: /\btable\b|\bmatrix\b|\bcompare?\s+(?:in\s+a\s+)?table\b/i, tag: "table" },
|
|
115
|
+
{ re: /\bbullets?\b|\bbullet\s+points?\b/i, tag: "bullets" },
|
|
116
|
+
{ re: /\b(short|tldr|quick|brief|concise|one[-\s]?liner)\b/i, tag: "short" },
|
|
117
|
+
{ re: /\b(long|detailed|exhaustive|comprehensive|in[-\s]?depth)\b/i, tag: "long" },
|
|
118
|
+
{ re: /\b(summary|summari[sz]e)\b/i, tag: "summary" },
|
|
119
|
+
{ re: /\b(list|enumerate)\b/i, tag: "list" },
|
|
120
|
+
{ re: /\b(json|yaml|markdown|md)\b/i, tag: "structured" },
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
function extractFormatHints(text: string): string[] {
|
|
124
|
+
const hits = new Set<string>();
|
|
125
|
+
for (const { re, tag } of FORMAT_TOKENS) if (re.test(text)) hits.add(tag);
|
|
126
|
+
return [...hits];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const EXCLUSION_RE = /\b(?:without|except|excluding|but\s+not|other\s+than)\s+([A-Za-z0-9 ,.\-]+?)(?=[.?!]|$)/gi;
|
|
130
|
+
|
|
131
|
+
function extractExclusions(text: string): string[] {
|
|
132
|
+
const ex: string[] = [];
|
|
133
|
+
let m: RegExpExecArray | null;
|
|
134
|
+
while ((m = EXCLUSION_RE.exec(text)) !== null) ex.push(m[1].trim());
|
|
135
|
+
return ex;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function extractConstraints(userText: string): ExtractedConstraints {
|
|
139
|
+
const timeHorizon = extractTimeHorizon(userText);
|
|
140
|
+
const { explicit, named } = extractTickers(userText);
|
|
141
|
+
const formatHints = extractFormatHints(userText);
|
|
142
|
+
const exclusions = extractExclusions(userText);
|
|
143
|
+
const domain = detectDomain(userText);
|
|
144
|
+
|
|
145
|
+
const namedEntitiesSet = new Set<string>([...explicit, ...named]);
|
|
146
|
+
const namedEntities = [...namedEntitiesSet];
|
|
147
|
+
|
|
148
|
+
const isShortHorizon = !!timeHorizon && timeHorizon.days <= 90;
|
|
149
|
+
const isLongHorizon = !!timeHorizon && timeHorizon.days >= 365;
|
|
150
|
+
|
|
151
|
+
let scopeSentence: string | undefined;
|
|
152
|
+
if (timeHorizon) {
|
|
153
|
+
scopeSentence = `User asked about a window of ~${timeHorizon.days} day${timeHorizon.days === 1 ? "" : "s"} ("${timeHorizon.rawMatch}").`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
timeHorizon,
|
|
158
|
+
namedEntities,
|
|
159
|
+
explicitComparisonTargets: explicit,
|
|
160
|
+
formatHints,
|
|
161
|
+
exclusions,
|
|
162
|
+
scopeSentence,
|
|
163
|
+
domain,
|
|
164
|
+
isShortHorizon,
|
|
165
|
+
isLongHorizon,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ────────────────────────────────────────────────────────────────────
|
|
170
|
+
// Constraint block for the synthesis prompt.
|
|
171
|
+
// This is the hard instruction the model receives, derived from the
|
|
172
|
+
// extracted constraints above. The model gets a small, explicit, and
|
|
173
|
+
// deterministic set of rules — no inference of "what the user meant".
|
|
174
|
+
|
|
175
|
+
export function buildConstraintBlock(c: ExtractedConstraints): string {
|
|
176
|
+
const lines: string[] = ["HARD CONSTRAINTS (you MUST respect, no exceptions):"];
|
|
177
|
+
|
|
178
|
+
if (c.timeHorizon) {
|
|
179
|
+
const d = c.timeHorizon.days;
|
|
180
|
+
lines.push(`- TIME WINDOW: The user is asking about a horizon of ~${d} day${d === 1 ? "" : "s"} ("${c.timeHorizon.rawMatch}").`);
|
|
181
|
+
if (c.isShortHorizon) {
|
|
182
|
+
lines.push(` • This is a SHORT, TACTICAL window. Long-term metrics MUST be excluded unless explicitly relevant.`);
|
|
183
|
+
lines.push(` • EXCLUDE: 1-year price targets, trailing 12-month returns, multi-year forecasts, distant-quarter projections.`);
|
|
184
|
+
lines.push(` • INCLUDE: catalysts/events inside the next ${d} days, near-term technical setup, current momentum, immediate macro headlines.`);
|
|
185
|
+
lines.push(` • If a catalyst (e.g. earnings) falls OUTSIDE this window, you MUST state explicitly that it is OUTSIDE the window and therefore not actionable.`);
|
|
186
|
+
} else if (c.isLongHorizon) {
|
|
187
|
+
lines.push(` • This is a LONG, STRATEGIC window. Day-to-day price action and intraday technicals are NOT decisive.`);
|
|
188
|
+
lines.push(` • Prefer multi-year fundamentals, secular trends, valuation framework, and structural risks.`);
|
|
189
|
+
} else {
|
|
190
|
+
lines.push(` • Filter all evidence to be meaningfully informative within ${d} days.`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (c.explicitComparisonTargets.length > 0) {
|
|
195
|
+
lines.push(`- EXPLICIT COMPARISON TARGET(S): ${c.explicitComparisonTargets.join(", ")}. The user asked specifically about these — address them by name first, then alternatives.`);
|
|
196
|
+
} else if (c.namedEntities.length > 0) {
|
|
197
|
+
lines.push(`- NAMED ENTITIES: ${c.namedEntities.join(", ")}. Keep these in focus.`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (c.formatHints.length > 0) {
|
|
201
|
+
lines.push(`- OUTPUT FORMAT: ${c.formatHints.join(", ")}.`);
|
|
202
|
+
if (c.formatHints.includes("table") || c.formatHints.includes("structured")) {
|
|
203
|
+
lines.push(` • Use a Markdown table for the comparison. Columns must be scannable.`);
|
|
204
|
+
}
|
|
205
|
+
if (c.formatHints.includes("short") || c.formatHints.includes("summary")) {
|
|
206
|
+
lines.push(` • Keep the answer brief. No throat-clearing.`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (c.exclusions.length > 0) {
|
|
211
|
+
lines.push(`- EXCLUSIONS: ${c.exclusions.join("; ")}. Do not include these.`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (c.domain === "financial") {
|
|
215
|
+
lines.push(`- DOMAIN GUARDRAIL (financial): This is informational, not investment advice. Mention major risks. Never imply guaranteed returns.`);
|
|
216
|
+
} else if (c.domain === "medical") {
|
|
217
|
+
lines.push(`- DOMAIN GUARDRAIL (medical): Not medical advice. Recommend consulting a clinician for individualized decisions.`);
|
|
218
|
+
} else if (c.domain === "legal") {
|
|
219
|
+
lines.push(`- DOMAIN GUARDRAIL (legal): Not legal advice. Note jurisdictional variance.`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
lines.push("");
|
|
223
|
+
lines.push("OUTPUT RULES (strict, non-negotiable):");
|
|
224
|
+
lines.push("- Begin with the first sentence of the substantive answer. NO preamble.");
|
|
225
|
+
lines.push("- DO NOT restate the user's question.");
|
|
226
|
+
lines.push("- DO NOT restate or label any constraints (no \"Constraints:\", \"Persona:\", \"Structure:\", \"Direct Answer:\", \"Question:\").");
|
|
227
|
+
lines.push("- DO NOT include an outline, numbered plan, or scratchpad before the prose.");
|
|
228
|
+
lines.push("- DO NOT include extracted-fact bullet dumps before the prose. Synthesize directly.");
|
|
229
|
+
lines.push("- DO NOT mention the source list as a list — cite inline like [Source N].");
|
|
230
|
+
lines.push("- If a piece of evidence falls outside the requested constraints, EITHER omit it OR explicitly mark it as out-of-window context.");
|
|
231
|
+
lines.push("- Every numeric/empirical claim must be inside the user's stated scope, or it must be flagged as out-of-scope.");
|
|
232
|
+
|
|
233
|
+
return lines.join("\n");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ────────────────────────────────────────────────────────────────────
|
|
237
|
+
// Output sanitizer.
|
|
238
|
+
// Strips leaked scratchpad/outline/constraint blocks from model output.
|
|
239
|
+
// The model SHOULD follow the rules above, but small models leak. This
|
|
240
|
+
// is a deterministic safety net.
|
|
241
|
+
|
|
242
|
+
const LEAK_PATTERNS: RegExp[] = [
|
|
243
|
+
// "Question: ..." / "User question: ..."
|
|
244
|
+
/^\s*\**\s*(?:User\s+)?Question\s*:\s*.*$/gim,
|
|
245
|
+
// "Constraints: ..."
|
|
246
|
+
/^\s*\**\s*Constraints?\s*:\s*.*$/gim,
|
|
247
|
+
// "Persona: ..."
|
|
248
|
+
/^\s*\**\s*Persona\s*:\s*.*$/gim,
|
|
249
|
+
// "Structure:" possibly followed by a numbered outline
|
|
250
|
+
/^\s*\**\s*Structure\s*:\s*[\s\S]*?(?=\n\s*\n|$)/gim,
|
|
251
|
+
// "Direct Answer:" header (scratchpad header style)
|
|
252
|
+
/^\s*\**\s*Direct\s+Answer\s*:\s*/gim,
|
|
253
|
+
// "Is NVDA the best?" header (scratchpad-restate style at top)
|
|
254
|
+
/^\s*\**\s*Is\s+[A-Z][A-Z0-9.\-]+\s+the\s+best\??\s*:\s*.*$/gim,
|
|
255
|
+
// Hidden XML/tag wrappers that should never appear in output
|
|
256
|
+
/<\/?(?:STYLE_INSTRUCTIONS|RETRIEVED_WEB_DATA|SECOND_PASS_VERIFICATION_RESULTS|HARD_CONSTRAINTS|SCRATCHPAD|THOUGHT|REASONING|PLAN|HIDDEN_REASONING)\b[^>]*>/gi,
|
|
257
|
+
// "Alternative Options: ..." / "The Hardware vs. Software split: ..."
|
|
258
|
+
/^\s*\**\s*Alternative\s+Options\s*:\s*.*$/gim,
|
|
259
|
+
// "Macro Factors:" scratchpad headers
|
|
260
|
+
/^\s*\**\s*Macro\s+Factors?\s*:\s*$/gim,
|
|
261
|
+
// "Catalysts: ..." scratchpad headers
|
|
262
|
+
/^\s*\**\s*(?:Catalysts?|Earnings|Sources?\s+found)\s*:\s*\d.*$/gim,
|
|
263
|
+
];
|
|
264
|
+
|
|
265
|
+
/** Patterns that indicate a leading scratchpad block. Find first prose start. */
|
|
266
|
+
const PROSE_BOUNDARY = [
|
|
267
|
+
/\n#{1,6}\s/, // first markdown heading
|
|
268
|
+
/\n\s*[A-Z][^*\n]{40,}/, // first long paragraph line that isn't a bullet
|
|
269
|
+
];
|
|
270
|
+
|
|
271
|
+
export interface SanitizeReport {
|
|
272
|
+
cleaned: string;
|
|
273
|
+
removedSegments: number;
|
|
274
|
+
detectedLeak: boolean;
|
|
275
|
+
notes: string[];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function sanitizeOutput(raw: string): SanitizeReport {
|
|
279
|
+
const notes: string[] = [];
|
|
280
|
+
let detectedLeak = false;
|
|
281
|
+
let cleaned = raw;
|
|
282
|
+
let removedSegments = 0;
|
|
283
|
+
|
|
284
|
+
// Strip per-line leak patterns.
|
|
285
|
+
for (const re of LEAK_PATTERNS) {
|
|
286
|
+
const before = cleaned;
|
|
287
|
+
cleaned = cleaned.replace(re, "");
|
|
288
|
+
if (cleaned !== before) {
|
|
289
|
+
removedSegments++;
|
|
290
|
+
detectedLeak = true;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// If the output BEGINS with a scratchpad-style bullet block (lots of "* X:" lines)
|
|
295
|
+
// before any prose, trim everything up to the first prose paragraph or heading.
|
|
296
|
+
const head = cleaned.slice(0, 1200);
|
|
297
|
+
const headIsBulletDump =
|
|
298
|
+
/^[\s\S]{0,30}\*\s+(?:\*+)?(?:Question|Constraints?|Persona|Structure|Direct\s+Answer|NVDA|MU|STX)/.test(head);
|
|
299
|
+
if (headIsBulletDump) {
|
|
300
|
+
let cutAt = -1;
|
|
301
|
+
for (const re of PROSE_BOUNDARY) {
|
|
302
|
+
const m = cleaned.match(re);
|
|
303
|
+
if (m && m.index !== undefined && (cutAt === -1 || m.index < cutAt)) cutAt = m.index;
|
|
304
|
+
}
|
|
305
|
+
if (cutAt > 0) {
|
|
306
|
+
cleaned = cleaned.slice(cutAt).trimStart();
|
|
307
|
+
detectedLeak = true;
|
|
308
|
+
removedSegments++;
|
|
309
|
+
notes.push("Stripped leading scratchpad bullet dump before first prose section.");
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Collapse runs of >2 blank lines created by stripping.
|
|
314
|
+
cleaned = cleaned.replace(/\n{3,}/g, "\n\n").trim();
|
|
315
|
+
|
|
316
|
+
if (detectedLeak) {
|
|
317
|
+
notes.push(`Sanitizer removed ${removedSegments} leaked segment(s) from model output.`);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return { cleaned, removedSegments, detectedLeak, notes };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Friendly one-line summary of the active constraints, for the UI badge.
|
|
324
|
+
export function summarizeConstraints(c: ExtractedConstraints): string {
|
|
325
|
+
const parts: string[] = [];
|
|
326
|
+
if (c.timeHorizon) parts.push(`${c.timeHorizon.value} ${c.timeHorizon.unit}${c.timeHorizon.value > 1 ? "s" : ""}`);
|
|
327
|
+
if (c.explicitComparisonTargets.length > 0) parts.push(`focus: ${c.explicitComparisonTargets.join("/")}`);
|
|
328
|
+
if (c.formatHints.length > 0) parts.push(`fmt: ${c.formatHints.join(",")}`);
|
|
329
|
+
if (c.domain && c.domain !== "general") parts.push(`domain: ${c.domain}`);
|
|
330
|
+
return parts.length > 0 ? parts.join(" · ") : "no explicit constraints";
|
|
331
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Continuation Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects when an LLM output was truncated mid-generation (hit max_tokens cap).
|
|
5
|
+
* Signs of truncation:
|
|
6
|
+
* - Multiple consecutive section headers with no body between them
|
|
7
|
+
* - Text ends mid-sentence (no terminal punctuation)
|
|
8
|
+
* - Text ends with an opening list marker or table row
|
|
9
|
+
* - Header-to-prose ratio is heavily skewed toward headers
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export interface TruncationDiagnosis {
|
|
13
|
+
truncated: boolean;
|
|
14
|
+
reason: string;
|
|
15
|
+
emptySections: string[]; // section headers that have no body after them
|
|
16
|
+
endsAbruptly: boolean; // text ends mid-sentence
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Find Markdown headers (## Foo, ### Bar) and check if each has a body.
|
|
21
|
+
* A section is considered "empty" if the text between this header and the
|
|
22
|
+
* next header (or end) is less than 40 chars of non-whitespace content.
|
|
23
|
+
*/
|
|
24
|
+
export function diagnoseOutput(text: string): TruncationDiagnosis {
|
|
25
|
+
if (!text || text.length < 100) {
|
|
26
|
+
return { truncated: false, reason: "too-short-to-diagnose", emptySections: [], endsAbruptly: false };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const lines = text.split("\n");
|
|
30
|
+
const headerIndices: Array<{ line: number; title: string }> = [];
|
|
31
|
+
|
|
32
|
+
for (let i = 0; i < lines.length; i++) {
|
|
33
|
+
const m = lines[i].match(/^#{1,4}\s+(.+)$/) || lines[i].match(/^\*\*(.+?)\*\*\s*$/);
|
|
34
|
+
if (m) {
|
|
35
|
+
headerIndices.push({ line: i, title: m[1].trim() });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const emptySections: string[] = [];
|
|
40
|
+
for (let i = 0; i < headerIndices.length; i++) {
|
|
41
|
+
const startLine = headerIndices[i].line + 1;
|
|
42
|
+
const endLine = i + 1 < headerIndices.length ? headerIndices[i + 1].line : lines.length;
|
|
43
|
+
const body = lines.slice(startLine, endLine).join(" ").replace(/\s+/g, " ").trim();
|
|
44
|
+
if (body.length < 40) {
|
|
45
|
+
emptySections.push(headerIndices[i].title);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Check abrupt termination: last 200 chars should end with terminal punctuation
|
|
50
|
+
const tail = text.trimEnd().slice(-200);
|
|
51
|
+
const endsAbruptly =
|
|
52
|
+
!/[.!?…”")\]]\s*$/.test(text.trimEnd()) &&
|
|
53
|
+
text.trimEnd().length > 200 &&
|
|
54
|
+
!/^#/.test(tail.split("\n").slice(-1)[0] ?? "");
|
|
55
|
+
|
|
56
|
+
const headerHeavyRatio = headerIndices.length / Math.max(1, lines.filter(l => l.trim().length > 0).length);
|
|
57
|
+
const tooManyEmpty = emptySections.length >= 2 && headerIndices.length >= 3;
|
|
58
|
+
|
|
59
|
+
const truncated = tooManyEmpty || endsAbruptly || (headerHeavyRatio > 0.5 && headerIndices.length >= 3);
|
|
60
|
+
const reason = truncated
|
|
61
|
+
? tooManyEmpty
|
|
62
|
+
? `${emptySections.length}/${headerIndices.length} sections are empty (likely truncated mid-generation)`
|
|
63
|
+
: endsAbruptly
|
|
64
|
+
? "output ends mid-sentence (likely hit max_tokens)"
|
|
65
|
+
: "header-to-body ratio is abnormally high"
|
|
66
|
+
: "output looks complete";
|
|
67
|
+
|
|
68
|
+
return { truncated, reason, emptySections, endsAbruptly };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Build a continuation prompt that asks the model to fill in only the
|
|
73
|
+
* specific empty sections rather than rewriting the whole answer.
|
|
74
|
+
*/
|
|
75
|
+
export function buildContinuationPrompt(
|
|
76
|
+
originalQuery: string,
|
|
77
|
+
truncatedDraft: string,
|
|
78
|
+
emptySections: string[],
|
|
79
|
+
): string {
|
|
80
|
+
return [
|
|
81
|
+
`The prior draft below was truncated and several sections were left as bare headers with no body.`,
|
|
82
|
+
`Fill in ONLY the missing section bodies for: ${emptySections.map(s => `"${s}"`).join(", ")}.`,
|
|
83
|
+
`Do NOT rewrite sections that already have content. Do NOT repeat the section headers in your response.`,
|
|
84
|
+
`Output the new section bodies in Markdown, prefixed with their section header so they can be spliced in.`,
|
|
85
|
+
`Think in neuralese internally; output only plain English.`,
|
|
86
|
+
``,
|
|
87
|
+
`USER ASK: ${originalQuery.slice(0, 400)}`,
|
|
88
|
+
``,
|
|
89
|
+
`TRUNCATED DRAFT (last 1500 chars for context):`,
|
|
90
|
+
truncatedDraft.slice(-1500),
|
|
91
|
+
].join("\n");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Splice continuation content back into the truncated draft.
|
|
96
|
+
* If a section header is found in both the draft and the continuation,
|
|
97
|
+
* the continuation's body replaces the draft's empty body.
|
|
98
|
+
*/
|
|
99
|
+
export function spliceContinuation(draft: string, continuation: string): string {
|
|
100
|
+
if (!continuation.trim()) return draft;
|
|
101
|
+
|
|
102
|
+
// Parse continuation into section -> body map
|
|
103
|
+
const contMap = new Map<string, string>();
|
|
104
|
+
const headerRe = /^(#{1,4}\s+.+)$/gm;
|
|
105
|
+
const parts = continuation.split(headerRe);
|
|
106
|
+
for (let i = 1; i < parts.length; i += 2) {
|
|
107
|
+
const header = parts[i].trim();
|
|
108
|
+
const body = (parts[i + 1] || "").trim();
|
|
109
|
+
if (body) contMap.set(header.replace(/^#+\s+/, "").trim().toLowerCase(), body);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (contMap.size === 0) {
|
|
113
|
+
// No structured continuation — just append it
|
|
114
|
+
return draft + "\n\n" + continuation;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Splice: for each empty header in the draft, find a matching continuation body
|
|
118
|
+
const draftLines = draft.split("\n");
|
|
119
|
+
const output: string[] = [];
|
|
120
|
+
for (let i = 0; i < draftLines.length; i++) {
|
|
121
|
+
output.push(draftLines[i]);
|
|
122
|
+
const headerMatch = draftLines[i].match(/^#{1,4}\s+(.+)$/);
|
|
123
|
+
if (headerMatch) {
|
|
124
|
+
const key = headerMatch[1].trim().toLowerCase();
|
|
125
|
+
const body = contMap.get(key);
|
|
126
|
+
if (body) {
|
|
127
|
+
// Check if next non-empty line is another header (empty section)
|
|
128
|
+
let j = i + 1;
|
|
129
|
+
while (j < draftLines.length && draftLines[j].trim() === "") j++;
|
|
130
|
+
const nextIsHeader = j >= draftLines.length || /^#{1,4}\s+/.test(draftLines[j]);
|
|
131
|
+
if (nextIsHeader) {
|
|
132
|
+
output.push("");
|
|
133
|
+
output.push(body);
|
|
134
|
+
output.push("");
|
|
135
|
+
contMap.delete(key);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Any remaining continuation content gets appended
|
|
142
|
+
if (contMap.size > 0) {
|
|
143
|
+
output.push("");
|
|
144
|
+
for (const [header, body] of contMap) {
|
|
145
|
+
output.push(`## ${header.charAt(0).toUpperCase() + header.slice(1)}`);
|
|
146
|
+
output.push("");
|
|
147
|
+
output.push(body);
|
|
148
|
+
output.push("");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return output.join("\n");
|
|
153
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Continuation Detector — persistent workspace override (Vite interceptor pattern).
|
|
3
|
+
*
|
|
4
|
+
* Confirmed gap in the REAL production truncation guard used by ChatApp.tsx's
|
|
5
|
+
* final safety net (runs after single-pass, 4-Stage, AND N-Deep paths): the
|
|
6
|
+
* base `diagnoseOutput()` only flags empty trailing sections when >=2 sections
|
|
7
|
+
* are empty AND there are >=3 total headers (`tooManyEmpty`), and separately
|
|
8
|
+
* its `endsAbruptly` check explicitly SKIPS the case where the last line is a
|
|
9
|
+
* heading (`!/^#/.test(...)`) — meaning a draft that ends on a single bare,
|
|
10
|
+
* bodyless section heading (e.g. "## Conclusion" with nothing after it, or any
|
|
11
|
+
* document with fewer than 3 headers total) is silently NOT flagged as
|
|
12
|
+
* truncated, so ChatApp.tsx's continuation-splice repair never engages and the
|
|
13
|
+
* truncated draft ships as final.
|
|
14
|
+
*
|
|
15
|
+
* Fix: purely additive OR-condition — a document whose LAST detected section
|
|
16
|
+
* heading has no body is always truncated, regardless of total header count.
|
|
17
|
+
* The base function's own detection (and its exact reason strings, emptySections
|
|
18
|
+
* list, and endsAbruptly semantics) are called FIRST and returned unchanged
|
|
19
|
+
* whenever it already correctly detects truncation; this override only adds
|
|
20
|
+
* coverage for the case it was missing.
|
|
21
|
+
*/
|
|
22
|
+
export * from "./continuation-detector.base";
|
|
23
|
+
import { diagnoseOutput as baseDiagnoseOutput, type TruncationDiagnosis } from "./continuation-detector.base";
|
|
24
|
+
|
|
25
|
+
export function diagnoseOutput(text: string): TruncationDiagnosis {
|
|
26
|
+
const base = baseDiagnoseOutput(text);
|
|
27
|
+
if (base.truncated || !text || text.length < 100) return base;
|
|
28
|
+
|
|
29
|
+
const lines = text.split("\n");
|
|
30
|
+
let lastHeaderIdx = -1;
|
|
31
|
+
let lastHeaderTitle = "";
|
|
32
|
+
for (let i = 0; i < lines.length; i++) {
|
|
33
|
+
const m = lines[i].match(/^#{1,4}\s+(.+)$/) || lines[i].match(/^\*\*(.+?)\*\*\s*$/);
|
|
34
|
+
if (m) { lastHeaderIdx = i; lastHeaderTitle = m[1].trim(); }
|
|
35
|
+
}
|
|
36
|
+
if (lastHeaderIdx === -1) return base;
|
|
37
|
+
|
|
38
|
+
const trailingBody = lines.slice(lastHeaderIdx + 1).join(" ").replace(/\s+/g, " ").trim();
|
|
39
|
+
if (trailingBody.length < 40) {
|
|
40
|
+
return {
|
|
41
|
+
truncated: true,
|
|
42
|
+
reason: `trailing section "${lastHeaderTitle}" has no body — draft likely truncated mid-generation`,
|
|
43
|
+
emptySections: [...new Set([...base.emptySections, lastHeaderTitle])],
|
|
44
|
+
endsAbruptly: true,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return base;
|
|
48
|
+
}
|