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,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MANUSCRIPT GRADE v2 — weighted 100-point editorial rubric (NEW, additive).
|
|
3
|
+
* ---------------------------------------------------------------------------
|
|
4
|
+
* Independent of, and non-destructive to, the existing 1-10 x 12-category
|
|
5
|
+
* `gradeManuscript` (node_modules/src/lib/sage/judge.ts) used by
|
|
6
|
+
* the in-panel "Grade manuscript" button — that pipeline is untouched.
|
|
7
|
+
*
|
|
8
|
+
* This module implements the SEPARATE weighted rubric requested for the
|
|
9
|
+
* standalone grading report:
|
|
10
|
+
* Theme & Conceptual Ambition 15%
|
|
11
|
+
* Worldbuilding & Setting Depth 15%
|
|
12
|
+
* Prose Style & Syntactic Craft 15%
|
|
13
|
+
* Character Voice & Coherence 20%
|
|
14
|
+
* Plot Architecture & Pacing 20%
|
|
15
|
+
* Cleanliness & Narrative Logic 15%
|
|
16
|
+
* ----
|
|
17
|
+
* 100%
|
|
18
|
+
*
|
|
19
|
+
* HONESTY CONTRACT
|
|
20
|
+
* - The composite score and letter-grade banding are DETERMINISTIC local
|
|
21
|
+
* arithmetic (never the model's own claim of a total).
|
|
22
|
+
* - The 100 sub-scores and prose commentary are a MODEL JUDGMENT, labelled
|
|
23
|
+
* as such everywhere in the UI — never presented as verified fact.
|
|
24
|
+
* - Rewrite suggestions are quote-grounded: every `originalQuote` is checked
|
|
25
|
+
* as a verbatim (whitespace-normalized) substring of the manuscript that
|
|
26
|
+
* was actually graded. A suggestion that fails this check is KEPT but
|
|
27
|
+
* flagged `verified:false` — never silently dropped, never silently
|
|
28
|
+
* trusted (zero-fabrication / quote-grounding discipline).
|
|
29
|
+
* - On a JSON parse failure the report is NOT fabricated: every category
|
|
30
|
+
* gets a neutral 50 and `parseError` is populated so the UI can show the
|
|
31
|
+
* raw judge text and flag the run as unreliable.
|
|
32
|
+
*
|
|
33
|
+
* NOTE ON THE "ADDITIONAL CRITERIA" SET
|
|
34
|
+
* No reference image was actually received in this session/tool context, so
|
|
35
|
+
* the additional-criteria list below is an original, professionally-derived
|
|
36
|
+
* diagnostic set (NOT reverse-engineered from an unseen attachment). This is
|
|
37
|
+
* disclosed in the UI to avoid an honesty violation.
|
|
38
|
+
*/
|
|
39
|
+
import { computeProseMetrics, type ProseMetrics } from '@/lib/sage/judge';
|
|
40
|
+
|
|
41
|
+
export type GradeV2CategoryKey = "theme" | "worldbuilding" | "prose" | "character" | "plot" | "cleanliness";
|
|
42
|
+
|
|
43
|
+
export interface GradeV2Category {
|
|
44
|
+
key: GradeV2CategoryKey;
|
|
45
|
+
label: string;
|
|
46
|
+
weightPct: number;
|
|
47
|
+
score: number;
|
|
48
|
+
weightedScore: number;
|
|
49
|
+
letterBand: string;
|
|
50
|
+
descriptor: string;
|
|
51
|
+
rationale: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface AdditionalCriterion {
|
|
55
|
+
key: string;
|
|
56
|
+
label: string;
|
|
57
|
+
score: number;
|
|
58
|
+
note: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface RewriteSuggestion {
|
|
62
|
+
id: string;
|
|
63
|
+
location: string;
|
|
64
|
+
category: GradeV2CategoryKey | "additional" | "general";
|
|
65
|
+
originalQuote: string;
|
|
66
|
+
rewrittenQuote: string;
|
|
67
|
+
rationale: string;
|
|
68
|
+
verified: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ManuscriptGradeReportV2 {
|
|
72
|
+
generatedAt: number;
|
|
73
|
+
provider: string;
|
|
74
|
+
modelId: string;
|
|
75
|
+
wordCount: number;
|
|
76
|
+
metrics: ProseMetrics;
|
|
77
|
+
categories: GradeV2Category[];
|
|
78
|
+
weightedComposite: number;
|
|
79
|
+
compositeLetterBand: string;
|
|
80
|
+
additionalCriteria: AdditionalCriterion[];
|
|
81
|
+
additionalCriteriaSourceNote: string;
|
|
82
|
+
rewriteSuggestions: RewriteSuggestion[];
|
|
83
|
+
tableText: string;
|
|
84
|
+
rawJudgeText: string;
|
|
85
|
+
parseError: string | null;
|
|
86
|
+
truncated: boolean;
|
|
87
|
+
truncatedFromChars: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const GRADE_V2_CATEGORY_DEFS: { key: GradeV2CategoryKey; label: string; weightPct: number }[] = [
|
|
91
|
+
{ key: "theme", label: "Theme & Conceptual Ambition", weightPct: 15 },
|
|
92
|
+
{ key: "worldbuilding", label: "Worldbuilding & Setting Depth", weightPct: 15 },
|
|
93
|
+
{ key: "prose", label: "Prose Style & Syntactic Craft", weightPct: 15 },
|
|
94
|
+
{ key: "character", label: "Character Voice & Coherence", weightPct: 20 },
|
|
95
|
+
{ key: "plot", label: "Plot Architecture & Pacing", weightPct: 20 },
|
|
96
|
+
{ key: "cleanliness", label: "Cleanliness & Narrative Logic", weightPct: 15 },
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
export const ADDITIONAL_CRITERIA: { key: string; label: string }[] = [
|
|
100
|
+
{ key: "dialogue", label: "Dialogue Authenticity & Subtext" },
|
|
101
|
+
{ key: "imagery", label: "Sensory / Imagery Precision" },
|
|
102
|
+
{ key: "showTell", label: "Show-vs-Tell Ratio" },
|
|
103
|
+
{ key: "continuity", label: "Continuity & Canon Consistency" },
|
|
104
|
+
{ key: "hooks", label: "Chapter-Hook / Cliffhanger Strength" },
|
|
105
|
+
{ key: "exposition", label: "Exposition & Info-Dump Load (higher = leaner)" },
|
|
106
|
+
{ key: "voiceDistinct", label: "Voice Distinctiveness Across POV / Cast" },
|
|
107
|
+
{ key: "genreContract", label: "Genre-Contract Fulfillment (promises paid off)" },
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
export const ADDITIONAL_CRITERIA_SOURCE_NOTE =
|
|
111
|
+
"[UNVERIFIED PROVENANCE] No image attachment was received in this session. This diagnostic set is an original, " +
|
|
112
|
+
"professionally-grounded craft checklist — not derived from an unseen upload. Scores here are supplementary and " +
|
|
113
|
+
"excluded from the 100% weighted composite above.";
|
|
114
|
+
|
|
115
|
+
export function letterBand(score: number): string {
|
|
116
|
+
const s = Math.max(0, Math.min(100, score));
|
|
117
|
+
if (s >= 97) return "A+";
|
|
118
|
+
if (s >= 93) return "A";
|
|
119
|
+
if (s >= 90) return "A-";
|
|
120
|
+
if (s >= 87) return "B+";
|
|
121
|
+
if (s >= 83) return "B";
|
|
122
|
+
if (s >= 80) return "B-";
|
|
123
|
+
if (s >= 77) return "C+";
|
|
124
|
+
if (s >= 73) return "C";
|
|
125
|
+
if (s >= 70) return "C-";
|
|
126
|
+
if (s >= 67) return "D+";
|
|
127
|
+
if (s >= 63) return "D";
|
|
128
|
+
if (s >= 60) return "D-";
|
|
129
|
+
return "F";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function normalize(s: string): string {
|
|
133
|
+
return s.replace(/\s+/g, " ").trim().toLowerCase();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const MAX_GRADE_CHARS = 200_000;
|
|
137
|
+
|
|
138
|
+
export function buildGradeV2Prompt(manuscriptText: string): { prompt: string; truncated: boolean; truncatedFromChars: number } {
|
|
139
|
+
const truncated = manuscriptText.length > MAX_GRADE_CHARS;
|
|
140
|
+
const body = truncated ? manuscriptText.slice(0, MAX_GRADE_CHARS) : manuscriptText;
|
|
141
|
+
const catList = GRADE_V2_CATEGORY_DEFS.map((c) => ` - "${c.key}" = "${c.label}" (weight ${c.weightPct}%)`).join("\n");
|
|
142
|
+
const addList = ADDITIONAL_CRITERIA.map((c) => ` - "${c.key}" = "${c.label}"`).join("\n");
|
|
143
|
+
const prompt = [
|
|
144
|
+
"MANUSCRIPT WEIGHTED EDITORIAL RUBRIC",
|
|
145
|
+
"You are a panel of senior acquisitions editors grading a full manuscript exactly as a real developmental edit / options memo would.",
|
|
146
|
+
"Score EACH of these six weighted categories from 1 to 100 (use the full range; do not default to a narrow 70-85 band; be a real critic):",
|
|
147
|
+
catList,
|
|
148
|
+
"",
|
|
149
|
+
"Also score these additional diagnostic criteria from 1 to 100 (supplementary craft signals — NOT part of the weighted composite):",
|
|
150
|
+
addList,
|
|
151
|
+
"",
|
|
152
|
+
"Then propose 6 to 14 REWRITE SUGGESTIONS spanning multiple chapters, prioritizing the weakest categories. Each suggestion MUST quote a SHORT VERBATIM excerpt (5-40 words, copied EXACTLY character-for-character from the manuscript — never paraphrase the quote) as \"originalQuote\", plus an exact \"rewrittenQuote\" (the precise replacement text a copy-editor would substitute in) and a one-sentence \"rationale\".",
|
|
153
|
+
"Respond with STRICT JSON only, no markdown fences, no commentary outside the JSON, matching exactly this shape:",
|
|
154
|
+
"{\"categories\":{\"theme\":{\"score\":n,\"descriptor\":\"...\",\"rationale\":\"...\"},\"worldbuilding\":{\"score\":n,\"descriptor\":\"...\",\"rationale\":\"...\"},\"prose\":{\"score\":n,\"descriptor\":\"...\",\"rationale\":\"...\"},\"character\":{\"score\":n,\"descriptor\":\"...\",\"rationale\":\"...\"},\"plot\":{\"score\":n,\"descriptor\":\"...\",\"rationale\":\"...\"},\"cleanliness\":{\"score\":n,\"descriptor\":\"...\",\"rationale\":\"...\"}},\"additional\":{\"dialogue\":{\"score\":n,\"note\":\"...\"},\"imagery\":{\"score\":n,\"note\":\"...\"},\"showTell\":{\"score\":n,\"note\":\"...\"},\"continuity\":{\"score\":n,\"note\":\"...\"},\"hooks\":{\"score\":n,\"note\":\"...\"},\"exposition\":{\"score\":n,\"note\":\"...\"},\"voiceDistinct\":{\"score\":n,\"note\":\"...\"},\"genreContract\":{\"score\":n,\"note\":\"...\"}},\"rewriteSuggestions\":[{\"location\":\"Chapter N, ...\",\"category\":\"plot\",\"originalQuote\":\"...\",\"rewrittenQuote\":\"...\",\"rationale\":\"...\"}]}",
|
|
155
|
+
"",
|
|
156
|
+
truncated
|
|
157
|
+
? `NOTE: the manuscript below was truncated to the first ${MAX_GRADE_CHARS.toLocaleString()} characters for this evaluation; grade only what you can read and do not invent content for the unseen remainder.`
|
|
158
|
+
: "",
|
|
159
|
+
"MANUSCRIPT:",
|
|
160
|
+
body,
|
|
161
|
+
]
|
|
162
|
+
.filter((line) => line !== "")
|
|
163
|
+
.join("\n");
|
|
164
|
+
return { prompt, truncated, truncatedFromChars: truncated ? manuscriptText.length : 0 };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function extractJson(raw: string): unknown {
|
|
168
|
+
const fenced = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
169
|
+
const candidate = fenced ? fenced[1] : raw;
|
|
170
|
+
const start = candidate.indexOf("{");
|
|
171
|
+
const end = candidate.lastIndexOf("}");
|
|
172
|
+
if (start === -1 || end === -1 || end <= start) throw new Error("no JSON object found in grade v2 response");
|
|
173
|
+
return JSON.parse(candidate.slice(start, end + 1));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function clampScore(v: unknown): number {
|
|
177
|
+
const n = Number(v);
|
|
178
|
+
return Number.isFinite(n) ? Math.max(1, Math.min(100, Math.round(n))) : 50;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function formatGradeTable(categories: GradeV2Category[], composite: number, compositeBand: string): string {
|
|
182
|
+
const bar = "=".repeat(98);
|
|
183
|
+
const dash = "-".repeat(98);
|
|
184
|
+
const header = `${"EVALUATION CRITERIA (WEIGHT)".padEnd(38)}${"SCORE (1-100)".padStart(14)}${"WEIGHTED SCORE".padStart(18)} QUALITATIVE GRADE`;
|
|
185
|
+
const rows = categories.map((c) => {
|
|
186
|
+
const left = `${c.label} (${c.weightPct}%)`.padEnd(38);
|
|
187
|
+
const score = `${c.score} / 100`.padStart(14);
|
|
188
|
+
const weighted = c.weightedScore.toFixed(2).padStart(18);
|
|
189
|
+
return `${left}${score}${weighted} ${c.letterBand} (${c.descriptor})`;
|
|
190
|
+
});
|
|
191
|
+
const total = `${"WEIGHTED COMPOSITE SCORE (100%)".padEnd(38)}${"".padStart(14)}${`${composite.toFixed(2)} / 100`.padStart(18)} GRADE: ${compositeBand}`;
|
|
192
|
+
return [bar, header, dash, ...rows, dash, total].join("\n");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function parseGradeV2Response(
|
|
196
|
+
raw: string,
|
|
197
|
+
manuscriptText: string,
|
|
198
|
+
): {
|
|
199
|
+
categories: GradeV2Category[];
|
|
200
|
+
additionalCriteria: AdditionalCriterion[];
|
|
201
|
+
rewriteSuggestions: RewriteSuggestion[];
|
|
202
|
+
parseError: string | null;
|
|
203
|
+
} {
|
|
204
|
+
try {
|
|
205
|
+
const parsed = extractJson(raw) as {
|
|
206
|
+
categories?: Record<string, { score?: unknown; descriptor?: unknown; rationale?: unknown }>;
|
|
207
|
+
additional?: Record<string, { score?: unknown; note?: unknown }>;
|
|
208
|
+
rewriteSuggestions?: unknown;
|
|
209
|
+
};
|
|
210
|
+
const categories: GradeV2Category[] = GRADE_V2_CATEGORY_DEFS.map((def) => {
|
|
211
|
+
const c = parsed.categories?.[def.key] ?? {};
|
|
212
|
+
const score = clampScore(c.score);
|
|
213
|
+
return {
|
|
214
|
+
key: def.key,
|
|
215
|
+
label: def.label,
|
|
216
|
+
weightPct: def.weightPct,
|
|
217
|
+
score,
|
|
218
|
+
weightedScore: Math.round(score * (def.weightPct / 100) * 100) / 100,
|
|
219
|
+
letterBand: letterBand(score),
|
|
220
|
+
descriptor: typeof c.descriptor === "string" && c.descriptor ? c.descriptor : "(no descriptor returned)",
|
|
221
|
+
rationale: typeof c.rationale === "string" ? c.rationale : "",
|
|
222
|
+
};
|
|
223
|
+
});
|
|
224
|
+
const additionalCriteria: AdditionalCriterion[] = ADDITIONAL_CRITERIA.map((def) => {
|
|
225
|
+
const a = parsed.additional?.[def.key];
|
|
226
|
+
return { key: def.key, label: def.label, score: clampScore(a?.score), note: typeof a?.note === "string" ? a.note : "" };
|
|
227
|
+
});
|
|
228
|
+
const msNorm = normalize(manuscriptText);
|
|
229
|
+
const rewriteSuggestions: RewriteSuggestion[] = Array.isArray(parsed.rewriteSuggestions)
|
|
230
|
+
? (parsed.rewriteSuggestions as unknown[])
|
|
231
|
+
.filter((t): t is Record<string, unknown> => !!t && typeof t === "object")
|
|
232
|
+
.map((t, i) => {
|
|
233
|
+
const originalQuote = String(t.originalQuote ?? "");
|
|
234
|
+
const verified = originalQuote.trim().length > 0 && msNorm.includes(normalize(originalQuote));
|
|
235
|
+
const category = String(t.category ?? "general");
|
|
236
|
+
const validCategory = ["theme", "worldbuilding", "prose", "character", "plot", "cleanliness", "additional", "general"].includes(category)
|
|
237
|
+
? (category as RewriteSuggestion["category"])
|
|
238
|
+
: "general";
|
|
239
|
+
return {
|
|
240
|
+
id: `rw-${i}-${Date.now()}`,
|
|
241
|
+
location: String(t.location ?? "unspecified"),
|
|
242
|
+
category: validCategory,
|
|
243
|
+
originalQuote,
|
|
244
|
+
rewrittenQuote: String(t.rewrittenQuote ?? ""),
|
|
245
|
+
rationale: String(t.rationale ?? ""),
|
|
246
|
+
verified,
|
|
247
|
+
};
|
|
248
|
+
})
|
|
249
|
+
: [];
|
|
250
|
+
return { categories, additionalCriteria, rewriteSuggestions, parseError: null };
|
|
251
|
+
} catch (e) {
|
|
252
|
+
const categories = GRADE_V2_CATEGORY_DEFS.map((def) => ({
|
|
253
|
+
key: def.key,
|
|
254
|
+
label: def.label,
|
|
255
|
+
weightPct: def.weightPct,
|
|
256
|
+
score: 50,
|
|
257
|
+
weightedScore: Math.round(50 * (def.weightPct / 100) * 100) / 100,
|
|
258
|
+
letterBand: letterBand(50),
|
|
259
|
+
descriptor: "(judge response unparsable)",
|
|
260
|
+
rationale: "",
|
|
261
|
+
}));
|
|
262
|
+
const additionalCriteria = ADDITIONAL_CRITERIA.map((def) => ({ key: def.key, label: def.label, score: 50, note: "" }));
|
|
263
|
+
return { categories, additionalCriteria, rewriteSuggestions: [], parseError: e instanceof Error ? e.message : String(e) };
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export type GenerateFn = (userMessage: string) => Promise<string>;
|
|
268
|
+
|
|
269
|
+
export async function gradeManuscriptV2(
|
|
270
|
+
manuscriptText: string,
|
|
271
|
+
modelMeta: { provider: string; modelId: string },
|
|
272
|
+
generate: GenerateFn,
|
|
273
|
+
): Promise<ManuscriptGradeReportV2> {
|
|
274
|
+
const metrics = computeProseMetrics(manuscriptText);
|
|
275
|
+
const { prompt, truncated, truncatedFromChars } = buildGradeV2Prompt(manuscriptText);
|
|
276
|
+
const raw = await generate(prompt);
|
|
277
|
+
const parsed = parseGradeV2Response(raw, manuscriptText);
|
|
278
|
+
const composite = Math.round(parsed.categories.reduce((a, c) => a + c.weightedScore, 0) * 100) / 100;
|
|
279
|
+
const compositeBand = letterBand(composite);
|
|
280
|
+
return {
|
|
281
|
+
generatedAt: Date.now(),
|
|
282
|
+
provider: modelMeta.provider,
|
|
283
|
+
modelId: modelMeta.modelId,
|
|
284
|
+
wordCount: metrics.wordCount,
|
|
285
|
+
metrics,
|
|
286
|
+
categories: parsed.categories,
|
|
287
|
+
weightedComposite: composite,
|
|
288
|
+
compositeLetterBand: compositeBand,
|
|
289
|
+
additionalCriteria: parsed.additionalCriteria,
|
|
290
|
+
additionalCriteriaSourceNote: ADDITIONAL_CRITERIA_SOURCE_NOTE,
|
|
291
|
+
rewriteSuggestions: parsed.rewriteSuggestions,
|
|
292
|
+
tableText: formatGradeTable(parsed.categories, composite, compositeBand),
|
|
293
|
+
rawJudgeText: raw,
|
|
294
|
+
parseError: parsed.parseError,
|
|
295
|
+
truncated,
|
|
296
|
+
truncatedFromChars,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Governor — realistic, browser-aware memory pressure signal.
|
|
3
|
+
* Purpose: prevent OOM crashes during heavy SLOOP/N-Deep/cluster runs by
|
|
4
|
+
* exposing a single shared signal that callers can consult to:
|
|
5
|
+
* - decide whether to start a new heavy step,
|
|
6
|
+
* - how many parallel slots to use this wave,
|
|
7
|
+
* - whether to skip optional steps (adversarial critique, OCR), and
|
|
8
|
+
* - how to size working draft strings.
|
|
9
|
+
*
|
|
10
|
+
* The governor is intentionally additive — it never disables features, it
|
|
11
|
+
* only caps concurrency / sizes when measured pressure crosses a threshold.
|
|
12
|
+
* All existing code paths still work without it.
|
|
13
|
+
*/
|
|
14
|
+
export type PressureLevel = "ok" | "elevated" | "warn" | "critical";
|
|
15
|
+
|
|
16
|
+
export interface MemoryReport {
|
|
17
|
+
/** Used JS heap, MB; 0 if unavailable. */
|
|
18
|
+
usedMB: number;
|
|
19
|
+
/** Raw browser heap ceiling, MB. */
|
|
20
|
+
limitMB: number;
|
|
21
|
+
/** Effective safe working limit for THIS app, MB. */
|
|
22
|
+
softLimitMB: number;
|
|
23
|
+
/** Used / raw limit, 0..1. */
|
|
24
|
+
pctHeap: number;
|
|
25
|
+
/** navigator.deviceMemory (GB) when available; null otherwise. */
|
|
26
|
+
deviceGB: number | null;
|
|
27
|
+
/** Approximate app-state memory, MB (caller-provided, optional). */
|
|
28
|
+
approxStateMB: number;
|
|
29
|
+
/** Composite pressure 0..1 incorporating heap + state + device. */
|
|
30
|
+
pressure: number;
|
|
31
|
+
/** Categorical band derived from the composite pressure. */
|
|
32
|
+
level: PressureLevel;
|
|
33
|
+
/** Realistic free MB budget we still trust to allocate. */
|
|
34
|
+
freeBudgetMB: number;
|
|
35
|
+
/** True when heap telemetry was actually available (Chrome/Edge). */
|
|
36
|
+
heapAvailable: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const SAFETY_MARGIN_MB = 80; // leave headroom for GC, fetches, snapshot
|
|
40
|
+
const STATE_TO_HEAP_SCALE = 2.4; // app-state objects pull in retained closures
|
|
41
|
+
const DEFAULT_DEVICE_LIMIT_MB = 1024; // fallback when nothing is exposed
|
|
42
|
+
|
|
43
|
+
// Synthetic allocation tracker
|
|
44
|
+
const allocations = new Map<string, number>();
|
|
45
|
+
|
|
46
|
+
export function recordAllocation(label: string, bytes: number): void {
|
|
47
|
+
if (!Number.isFinite(bytes) || bytes <= 0) {
|
|
48
|
+
allocations.delete(label);
|
|
49
|
+
} else {
|
|
50
|
+
allocations.set(label, Math.round(bytes));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function clearAllocation(label: string): void {
|
|
55
|
+
allocations.delete(label);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function clearAllocationsByPrefix(prefix: string): void {
|
|
59
|
+
for (const key of Array.from(allocations.keys())) {
|
|
60
|
+
if (key.startsWith(prefix)) allocations.delete(key);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getSyntheticMB(): number {
|
|
65
|
+
let total = 0;
|
|
66
|
+
for (const v of allocations.values()) total += v;
|
|
67
|
+
return Math.round(total / 1048576);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function yieldForGc(ms = 0): Promise<void> {
|
|
71
|
+
await new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function settleHeap(ms = 25): Promise<void> {
|
|
75
|
+
await yieldForGc(0);
|
|
76
|
+
await yieldForGc(ms);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function readMemoryReport(approxStateMB = 0): MemoryReport {
|
|
80
|
+
const deviceGB =
|
|
81
|
+
typeof (navigator as any).deviceMemory === "number"
|
|
82
|
+
? ((navigator as any).deviceMemory as number)
|
|
83
|
+
: null;
|
|
84
|
+
|
|
85
|
+
let usedMB = 0;
|
|
86
|
+
let limitMB = 0;
|
|
87
|
+
let heapAvailable = false;
|
|
88
|
+
try {
|
|
89
|
+
const mem = (performance as any).memory;
|
|
90
|
+
if (mem) {
|
|
91
|
+
heapAvailable = true;
|
|
92
|
+
usedMB = Math.round(mem.usedJSHeapSize / 1048576);
|
|
93
|
+
limitMB = Math.round(mem.jsHeapSizeLimit / 1048576);
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
/* ignore */
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Heuristic limit when JS heap telemetry is unavailable (Firefox/Safari).
|
|
100
|
+
if (limitMB <= 0) {
|
|
101
|
+
if (deviceGB && deviceGB > 0) limitMB = Math.min(2048, deviceGB * 256);
|
|
102
|
+
else limitMB = DEFAULT_DEVICE_LIMIT_MB;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Effective safe working limit.
|
|
106
|
+
const softLimitMB = Math.max(
|
|
107
|
+
256,
|
|
108
|
+
Math.min(
|
|
109
|
+
2048,
|
|
110
|
+
Math.round(limitMB * 0.4),
|
|
111
|
+
deviceGB ? Math.round(deviceGB * 1024 * 0.35) : 1024,
|
|
112
|
+
),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const synthMB = getSyntheticMB();
|
|
116
|
+
const effectiveUsedMB = Math.max(usedMB, synthMB);
|
|
117
|
+
|
|
118
|
+
const pctHeap = limitMB > 0 ? Math.min(1, effectiveUsedMB / limitMB) : 0;
|
|
119
|
+
const effectiveHeapPressure = softLimitMB > 0 ? Math.min(1, effectiveUsedMB / softLimitMB) : pctHeap;
|
|
120
|
+
const statePressure = softLimitMB > 0 ? Math.min(1, (approxStateMB * STATE_TO_HEAP_SCALE) / softLimitMB) : 0;
|
|
121
|
+
// Composite — take the worst signal and round to 2dp.
|
|
122
|
+
const pressure = Math.min(1, Math.max(effectiveHeapPressure, statePressure));
|
|
123
|
+
const level: PressureLevel =
|
|
124
|
+
pressure >= 0.9 ? "critical" : pressure >= 0.75 ? "warn" : pressure >= 0.55 ? "elevated" : "ok";
|
|
125
|
+
|
|
126
|
+
const freeBudgetMB = Math.max(0, Math.round(softLimitMB - effectiveUsedMB - SAFETY_MARGIN_MB));
|
|
127
|
+
return { usedMB: effectiveUsedMB, limitMB, softLimitMB, pctHeap, deviceGB, approxStateMB, pressure, level, freeBudgetMB, heapAvailable };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Reusable safe-cap helpers used by SLOOP / N-Deep / cluster waves.
|
|
132
|
+
* Design: these are now PERMISSIVE. Because each transient buffer is flattened
|
|
133
|
+
* and freed between iterations (streaming consolidation + settleHeap), the
|
|
134
|
+
* marginal heap cost of an extra SLOOP page / N-Deep pass / cluster lane is
|
|
135
|
+
* small and bounded.
|
|
136
|
+
*/
|
|
137
|
+
export function safeClusterWidth(requested: number, report = readMemoryReport()): number {
|
|
138
|
+
if (report.level === "critical") return Math.min(requested, 2);
|
|
139
|
+
if (report.level === "warn") return Math.min(requested, 3);
|
|
140
|
+
if (report.level === "elevated") return Math.min(requested, 4);
|
|
141
|
+
// Keep all hypotheses, but run large clusters in safe waves to avoid the
|
|
142
|
+
// scraper/proxy fan-out that caused browser OOMs.
|
|
143
|
+
return Math.min(requested, 4);
|
|
144
|
+
}
|
|
145
|
+
export function safeSloopPages(requested: number, report = readMemoryReport()): number {
|
|
146
|
+
if (report.level === "critical") return Math.min(requested, 6);
|
|
147
|
+
return requested;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function safeNDeepPasses(requested: number, _sourceChars = 0, report = readMemoryReport()): number {
|
|
151
|
+
let cap = requested;
|
|
152
|
+
if (report.level === "critical") cap = Math.min(cap, 4);
|
|
153
|
+
if (report.freeBudgetMB < 64) cap = Math.min(cap, 2);
|
|
154
|
+
return Math.max(1, cap);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function safeDraftCharCap(report = readMemoryReport()): number {
|
|
158
|
+
const bytesPerChar = 2;
|
|
159
|
+
const buffersInFlight = 3;
|
|
160
|
+
const budgetBytes = Math.max(1, report.freeBudgetMB) * 1024 * 1024;
|
|
161
|
+
const charBudget = Math.floor(budgetBytes / (bytesPerChar * buffersInFlight));
|
|
162
|
+
return Math.max(24_000, Math.min(90_000, charBudget));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function wouldExceedBudget(extraMB: number, report = readMemoryReport()): boolean {
|
|
166
|
+
return extraMB > report.freeBudgetMB;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function shouldSkipAdversarial(draftChars: number, report = readMemoryReport()): boolean {
|
|
170
|
+
if (report.level === "critical") return true;
|
|
171
|
+
if (report.level === "warn" && draftChars > 20_000) return true;
|
|
172
|
+
return false;
|
|
173
|
+
}
|