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,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CONTINUITY GOVERNOR — deterministic world-state ledger + craft budgets.
|
|
3
|
+
* ---------------------------------------------------------------------------
|
|
4
|
+
* Purely local, no model call. Reads the prose the model ACTUALLY returned for
|
|
5
|
+
* chapters 1..N-1 and compiles a compact, machine-derived state block that is
|
|
6
|
+
* appended to the chapter-N request. Every line it emits is traceable to an
|
|
7
|
+
* extracted span, never invented.
|
|
8
|
+
*
|
|
9
|
+
* Targets the observed live failure modes:
|
|
10
|
+
* FM-02 duplicate opening collision -> CONTINUE-FROM lock + closing line
|
|
11
|
+
* FM-03 procedural deadlock -> change-vector requirement
|
|
12
|
+
* FM-05 wound/motif re-statement -> motif cooldown from real counts
|
|
13
|
+
* FM-06 "The The" title bug -> normalizeChapterTitle
|
|
14
|
+
* FM-07 citation bracket leakage -> stripHarnessResidue
|
|
15
|
+
* FM-08 POV drift -> POV lock + measured violations
|
|
16
|
+
* FM-09 climax evasion -> act ladder obligations
|
|
17
|
+
* FM-10 spatial whiplash -> place history + transit rule
|
|
18
|
+
* FM-11 concept overload -> new-concept budget
|
|
19
|
+
* FM-12 duplicate relational beat -> beat ledger
|
|
20
|
+
* FM-13 temporal claustrophobia -> macro clock ledger
|
|
21
|
+
* FM-14 instrument fetishism -> measurement budget
|
|
22
|
+
* FM-15 compulsive currency audits -> currency budget
|
|
23
|
+
* FM-16 zero-stakes resets -> irreversible-commitment ledger
|
|
24
|
+
*/
|
|
25
|
+
import type { SageBlueprint } from '@/lib/sage/engine';
|
|
26
|
+
import type { StorySnapshot, ContinuityToggles } from "./types";
|
|
27
|
+
import { DEFAULT_CONTINUITY_TOGGLES } from "./types";
|
|
28
|
+
|
|
29
|
+
export type ChapterFacts = {
|
|
30
|
+
chapter: number;
|
|
31
|
+
title: string;
|
|
32
|
+
words: number;
|
|
33
|
+
places: string[];
|
|
34
|
+
clocks: string[];
|
|
35
|
+
dates: string[];
|
|
36
|
+
irreversible: string[];
|
|
37
|
+
currency: string[];
|
|
38
|
+
measurements: string[];
|
|
39
|
+
concepts: string[];
|
|
40
|
+
motifHits: Record<string, number>;
|
|
41
|
+
residue: string[];
|
|
42
|
+
povViolations: number;
|
|
43
|
+
dominantVerbs: string[];
|
|
44
|
+
closingLine: string;
|
|
45
|
+
openingLine: string;
|
|
46
|
+
/**
|
|
47
|
+
* The upstream continuity excerpt, preserved byte-for-byte: the first 420
|
|
48
|
+
* characters of the chapter with whitespace collapsed. The base compiler is
|
|
49
|
+
* fed exactly this, unchanged from the original pipeline.
|
|
50
|
+
*/
|
|
51
|
+
excerpt420: string;
|
|
52
|
+
};
|
|
53
|
+
export type ContinuityLedger = {
|
|
54
|
+
facts: ChapterFacts[];
|
|
55
|
+
motifTotals: Record<string, number>;
|
|
56
|
+
conceptFirstSeen: Record<string, number>;
|
|
57
|
+
placeHistory: string[];
|
|
58
|
+
irreversibleAll: string[];
|
|
59
|
+
lastClock: string | null;
|
|
60
|
+
lastDate: string | null;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const EMPTY_LEDGER: ContinuityLedger = {
|
|
64
|
+
facts: [], motifTotals: {}, conceptFirstSeen: {}, placeHistory: [], irreversibleAll: [], lastClock: null, lastDate: null,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// ── residue / title hygiene (deterministic, reversible-by-inspection) ───────
|
|
68
|
+
export const RESIDUE_PATTERNS: { id: string; re: RegExp }[] = [
|
|
69
|
+
{ id: "source-bracket", re: /\[\s*Sources?\s*:?\s*\d+[^\]]{0,40}\]/gi },
|
|
70
|
+
{ id: "citation-tag", re: /\[\s*(?:S\d+|CTX|SRC|REF)\s*\d*\s*\]/gi },
|
|
71
|
+
{ id: "checksum-tag", re: /\[\s*CHECKSUM[^\]]{0,60}\]/gi },
|
|
72
|
+
{ id: "status-tag", re: /\[\s*(?:UNVERIFIED|VERIFIED|ASSUM|ASSUMPTION|DATA GAP|PROPOSED|POST-CUTOFF|DRAFT GAP|TODO|TBD)[^\]]{0,60}\]/gi },
|
|
73
|
+
{ id: "harness-error", re: /^.*\b(?:GoogleGenerativeAI|fetch failed|ECONNRESET|finish_reason|FinishReason|HTTP \d{3} error)\b.*$/gim },
|
|
74
|
+
{ id: "scratchpad", re: /^\s*(?:<\/?(?:scratchpad|thinking|plan)>|SCRATCHPAD:|PLAN:|INTERNAL NOTES?:).*$/gim },
|
|
75
|
+
{ id: "resumption-notice", re: /^_?Resuming at chapter \d+;?.*finished chapter.*restored.*$/gim },
|
|
76
|
+
{ id: "harness-preamble", re: /^.*(?:chapter\(s\) restored, not regenerated|_SAGE fiction run started:).*$/gim },
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
/** Removes harness residue. Returns cleaned text plus the ids actually hit. */
|
|
80
|
+
export function stripHarnessResidue(text: string): { text: string; removed: string[] } {
|
|
81
|
+
let out = text;
|
|
82
|
+
const removed: string[] = [];
|
|
83
|
+
for (const { id, re } of RESIDUE_PATTERNS) {
|
|
84
|
+
re.lastIndex = 0;
|
|
85
|
+
if (re.test(out)) { removed.push(id); re.lastIndex = 0; out = out.replace(re, ""); }
|
|
86
|
+
}
|
|
87
|
+
out = out.replace(/[ \t]{2,}/g, " ").replace(/ +([,.;:!?])/g, "$1").replace(/\n{3,}/g, "\n\n").trim();
|
|
88
|
+
return { text: out, removed };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** FM-06: collapses "The The Reckoning" / "A A Gate" produced by naive title templates. */
|
|
92
|
+
export function normalizeChapterTitle(title: string): string {
|
|
93
|
+
return title
|
|
94
|
+
.replace(/\b(The)\s+(?:The)\b/gi, (_match, first: string) => first)
|
|
95
|
+
.replace(/\b(An?)\s+(?:An?)\b/gi, (_match, first: string) => first)
|
|
96
|
+
.replace(/\s{2,}/g, " ")
|
|
97
|
+
.replace(/\s+([,.;:])/g, "$1")
|
|
98
|
+
.trim();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ── extraction ──────────────────────────────────────────────────────────────
|
|
102
|
+
const CLOCK_RE = /\b([01]?\d|2[0-3]):([0-5]\d)\b/g;
|
|
103
|
+
const DATE_RE = /\b(?:\d{1,2}(?:st|nd|rd|th)?\s+(?:of\s+)?[A-Z][a-z]{2,12}|[A-Z][a-z]{2,12}\s+\d{1,2},?\s*\d{0,4}|day\s+\d{1,3})\b/g;
|
|
104
|
+
const CURRENCY_RE = /\b\d[\d,.]*\s*(?:gold|silver|copper|bronze)?\s*(?:marks?|sovereigns?|crowns?|pieces?|francs?|credits?|coins?|shillings?|dollars?|euros?|pounds?)\b/gi;
|
|
105
|
+
const MEASURE_RE = /\b\d[\d,.]*\s*(?:hertz|hz|decibels?|db|millimet(?:er|re)s?|mm|centimet(?:er|re)s?|cm|met(?:er|re)s?|kilomet(?:er|re)s?|km|gram(?:me)?s?|kilogram(?:me)?s?|kg|milligrams?|mg|degrees?|celsius|fahrenheit|milligauss|millirads?|millibars?|picoseconds?|milliseconds?|percent|%|lumens?|lux|newtons?|pascals?|volts?|amps?|ohms?)\b/gi;
|
|
106
|
+
const IRREVERSIBLE_RE = /[^.!?\n]*\b(?:died|dies|dead|killed|kills|slain|corpse|buried|drowned|destroyed|burned down|burnt|shattered|collapsed|sank|severed|amputated|exiled|banished|forfeited|surrendered|signed away|sold|betrayed|confessed|revealed|arrested|imprisoned|seized|foreclosed|evicted|resigned|abandoned)\b[^.!?\n]*[.!?]/gi;
|
|
107
|
+
const VERB_RE = /\b(measured|inspected|audited|checked|recorded|noted|calibrated|examined|reviewed|filed|signed|walked|entered|descended|climbed|argued|negotiated|waited|counted|logged)\b/gi;
|
|
108
|
+
|
|
109
|
+
function uniq(list: string[], cap: number): string[] {
|
|
110
|
+
return [...new Set(list.map((entry) => entry.trim()).filter(Boolean))].slice(0, cap);
|
|
111
|
+
}
|
|
112
|
+
function matches(text: string, re: RegExp, cap = 40): string[] {
|
|
113
|
+
re.lastIndex = 0;
|
|
114
|
+
return uniq([...text.matchAll(re)].map((match) => match[0]), cap);
|
|
115
|
+
}
|
|
116
|
+
function firstSentence(text: string): string {
|
|
117
|
+
const value = text.trim().replace(/\s+/g, " ");
|
|
118
|
+
return (value.match(/^[^.!?]{0,240}[.!?]/)?.[0] ?? value.slice(0, 200)).trim();
|
|
119
|
+
}
|
|
120
|
+
function lastSentence(text: string): string {
|
|
121
|
+
const value = text.trim().replace(/\s+/g, " ");
|
|
122
|
+
const parts = value.match(/[^.!?]{0,240}[.!?]/g);
|
|
123
|
+
return (parts?.at(-1) ?? value.slice(-200)).trim();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Proper-noun-ish worldbuilding concepts: Title Case runs of 2-5 words. */
|
|
127
|
+
function extractConcepts(text: string, known: string[]): string[] {
|
|
128
|
+
const found = [...text.matchAll(/\b(?:[A-Z][a-z]{2,14})(?:\s+(?:of\s+|the\s+|and\s+)?[A-Z][a-z]{2,14}){1,4}\b/g)].map((match) => match[0].trim());
|
|
129
|
+
const knownLower = new Set(known.map((entry) => entry.toLowerCase()));
|
|
130
|
+
return uniq(found.filter((entry) => !knownLower.has(entry.toLowerCase()) && !/^(?:The|And|But|He|She|They|You|It|His|Her|Their)\b/.test(entry)), 24);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** POV target derived from the frozen blueprint; violations are counted, never silently rewritten. */
|
|
134
|
+
export function povProfile(pov: string): { label: string; forbidden: RegExp | null } {
|
|
135
|
+
const value = (pov || "").toLowerCase();
|
|
136
|
+
if (/second/.test(value)) return { label: "second person (you)", forbidden: /\b(?:he|she|him|her|his|hers)\b/gi };
|
|
137
|
+
if (/first/.test(value)) return { label: "first person (I/we)", forbidden: /\byou\b/gi };
|
|
138
|
+
return { label: pov || "third person", forbidden: /\byou\b/gi };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Static phrases the upstream contract repeats every chapter; these are what FM-05 surfaces verbatim. */
|
|
142
|
+
export function motifPhrases(bp: SageBlueprint): string[] {
|
|
143
|
+
const raw = [
|
|
144
|
+
bp.protagonist.wound, bp.protagonist.want, bp.protagonist.need,
|
|
145
|
+
bp.deuteragonist.wound, bp.antagonist.wound, bp.theme, bp.dramaticQuestion,
|
|
146
|
+
bp.worldMechanism?.name, bp.beatMechanism?.name, bp.relationshipMechanism?.name,
|
|
147
|
+
].filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 8);
|
|
148
|
+
return uniq(raw.map((entry) => entry.replace(/\s+/g, " ").trim()), 12);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function extractChapterFacts(args: {
|
|
152
|
+
chapter: number; title: string; text: string; snapshot: StorySnapshot;
|
|
153
|
+
}): ChapterFacts {
|
|
154
|
+
const { chapter, title, text, snapshot } = args;
|
|
155
|
+
const bp = snapshot.blueprint;
|
|
156
|
+
const placeNames = [
|
|
157
|
+
bp.primarySetting.name, ...bp.secondarySettings.map((setting) => setting.name),
|
|
158
|
+
...snapshot.roll.world.map((place) => place.label),
|
|
159
|
+
].filter(Boolean);
|
|
160
|
+
const castNames = snapshot.roll.cast.cast.map((member) => member.name).filter(Boolean);
|
|
161
|
+
const motifHits: Record<string, number> = {};
|
|
162
|
+
const lower = text.toLowerCase();
|
|
163
|
+
for (const phrase of motifPhrases(bp)) {
|
|
164
|
+
const needle = phrase.toLowerCase();
|
|
165
|
+
let count = 0, from = 0;
|
|
166
|
+
while (needle && from <= lower.length) {
|
|
167
|
+
const at = lower.indexOf(needle, from);
|
|
168
|
+
if (at < 0) break;
|
|
169
|
+
count++; from = at + needle.length;
|
|
170
|
+
}
|
|
171
|
+
if (count > 0) motifHits[phrase] = count;
|
|
172
|
+
}
|
|
173
|
+
const povRule = povProfile(bp.pov);
|
|
174
|
+
const povViolations = povRule.forbidden ? (text.match(povRule.forbidden)?.length ?? 0) : 0;
|
|
175
|
+
const residue: string[] = [];
|
|
176
|
+
for (const { id, re } of RESIDUE_PATTERNS) { re.lastIndex = 0; if (re.test(text)) residue.push(id); }
|
|
177
|
+
return {
|
|
178
|
+
chapter, title, words: (text.trim().match(/\S+/g) ?? []).length,
|
|
179
|
+
places: uniq(placeNames.filter((name) => lower.includes(name.toLowerCase())), 8),
|
|
180
|
+
clocks: matches(text, CLOCK_RE, 12),
|
|
181
|
+
dates: matches(text, DATE_RE, 8),
|
|
182
|
+
irreversible: matches(text, IRREVERSIBLE_RE, 8).map((line) => line.trim().slice(0, 180)),
|
|
183
|
+
currency: matches(text, CURRENCY_RE, 20),
|
|
184
|
+
measurements: matches(text, MEASURE_RE, 30),
|
|
185
|
+
concepts: extractConcepts(text, [...placeNames, ...castNames]),
|
|
186
|
+
motifHits, residue, povViolations,
|
|
187
|
+
dominantVerbs: matches(text, VERB_RE, 8).map((verb) => verb.toLowerCase()),
|
|
188
|
+
closingLine: lastSentence(text),
|
|
189
|
+
openingLine: firstSentence(text),
|
|
190
|
+
excerpt420: text.slice(0, 420).replace(/\s+/g, " "),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function capRecord(rec: Record<string, number>, cap: number): Record<string, number> {
|
|
195
|
+
const keys = Object.keys(rec);
|
|
196
|
+
if (keys.length <= cap) return rec;
|
|
197
|
+
// Keep highest-count entries: bounded memory at max scale (80 chapters).
|
|
198
|
+
const sorted = keys.sort((a, b) => rec[b] - rec[a]).slice(0, cap);
|
|
199
|
+
const out: Record<string, number> = {};
|
|
200
|
+
for (const k of sorted) out[k] = rec[k];
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function foldFacts(ledger: ContinuityLedger, facts: ChapterFacts): ContinuityLedger {
|
|
205
|
+
const motifTotals = { ...ledger.motifTotals };
|
|
206
|
+
for (const [phrase, count] of Object.entries(facts.motifHits)) motifTotals[phrase] = (motifTotals[phrase] ?? 0) + count;
|
|
207
|
+
const conceptFirstSeen = { ...ledger.conceptFirstSeen };
|
|
208
|
+
for (const concept of facts.concepts) if (conceptFirstSeen[concept] === undefined) conceptFirstSeen[concept] = facts.chapter;
|
|
209
|
+
const cappedMotifs = capRecord(motifTotals, 120);
|
|
210
|
+
const cappedConcepts = capRecord(conceptFirstSeen, 200);
|
|
211
|
+
return {
|
|
212
|
+
facts: [...ledger.facts, facts],
|
|
213
|
+
motifTotals: cappedMotifs, conceptFirstSeen: cappedConcepts,
|
|
214
|
+
placeHistory: [...ledger.placeHistory, ...facts.places].slice(-60),
|
|
215
|
+
irreversibleAll: [...ledger.irreversibleAll, ...facts.irreversible].slice(-40),
|
|
216
|
+
lastClock: facts.clocks.at(-1) ?? ledger.lastClock,
|
|
217
|
+
lastDate: facts.dates.at(-1) ?? ledger.lastDate,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Rebuilds the ledger from stored chapters after a reload/resume. Pure. */
|
|
222
|
+
export function rebuildLedger(snapshot: StorySnapshot, chapters: { index: number; title: string; text: string }[]): ContinuityLedger {
|
|
223
|
+
return chapters.reduce<ContinuityLedger>(
|
|
224
|
+
(ledger, chapter) => foldFacts(ledger, extractChapterFacts({ chapter: chapter.index, title: chapter.title, text: chapter.text, snapshot })),
|
|
225
|
+
EMPTY_LEDGER,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ── act ladder (FM-09 climax evasion, FM-03 stasis) ─────────────────────────
|
|
230
|
+
export function actObligation(chapterIndex: number, total: number): { act: string; duty: string } {
|
|
231
|
+
const position = total <= 1 ? 1 : chapterIndex / (total - 1);
|
|
232
|
+
if (position < 0.12) return { act: "ACT I — setup", duty: "End on a concrete disturbance that cannot be undone by paperwork." };
|
|
233
|
+
if (position < 0.28) return { act: "ACT I — lock-in", duty: "The protagonist must commit to a course with a stated cost they cannot refund." };
|
|
234
|
+
if (position < 0.48) return { act: "ACT II-A — rising cost", duty: "The opposition must take a coercive action that removes an option permanently." };
|
|
235
|
+
if (position < 0.56) return { act: "MIDPOINT — reversal", duty: "Reverse the protagonist's understanding or status; something they relied on is gone for good." };
|
|
236
|
+
if (position < 0.78) return { act: "ACT II-B — escalation", duty: "The opposition wins a round on the page. A named person, place or asset is lost." };
|
|
237
|
+
if (position < 0.9) return { act: "ACT III — crisis", duty: "Force a choice between two things the protagonist has protected all book; one must be sacrificed." };
|
|
238
|
+
if (position < 0.97) return { act: "ACT III — climax", duty: "Stage the direct confrontation the book promised, in scene, with the antagonist present and acting. No hearing, memo, audit or petition may substitute for it." };
|
|
239
|
+
return { act: "ACT III — resolution", duty: "Show the changed state that the climax bought, and pay the dramatic question in dramatized action." };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export type CraftBudgets = { measurements: number; currency: number; newConcepts: number; motifCap: number };
|
|
243
|
+
export const DEFAULT_BUDGETS: CraftBudgets = { measurements: 6, currency: 2, newConcepts: 2, motifCap: 2 };
|
|
244
|
+
|
|
245
|
+
/** Builds the additive continuity block appended to a chapter request, honoring individual toggles. */
|
|
246
|
+
export function buildContinuityBlock(args: {
|
|
247
|
+
snapshot: StorySnapshot;
|
|
248
|
+
chapterIndex: number;
|
|
249
|
+
ledger: ContinuityLedger;
|
|
250
|
+
budgets?: CraftBudgets;
|
|
251
|
+
toggles?: ContinuityToggles;
|
|
252
|
+
}): string {
|
|
253
|
+
const { snapshot, chapterIndex, ledger } = args;
|
|
254
|
+
const budgets = args.budgets ?? DEFAULT_BUDGETS;
|
|
255
|
+
const toggles = args.toggles ?? DEFAULT_CONTINUITY_TOGGLES;
|
|
256
|
+
const bp = snapshot.blueprint;
|
|
257
|
+
const total = bp.chapters.length;
|
|
258
|
+
const previous = ledger.facts.at(-1);
|
|
259
|
+
const ladder = actObligation(chapterIndex, total);
|
|
260
|
+
const povRule = povProfile(bp.pov);
|
|
261
|
+
const recentPlaces = uniq(ledger.placeHistory.slice(-3), 3);
|
|
262
|
+
const overusedMotifs = Object.entries(ledger.motifTotals)
|
|
263
|
+
.filter(([, count]) => count >= budgets.motifCap)
|
|
264
|
+
.map(([phrase]) => phrase).slice(0, 8);
|
|
265
|
+
const liveConcepts = Object.keys(ledger.conceptFirstSeen).slice(-10);
|
|
266
|
+
const lines: string[] = [];
|
|
267
|
+
|
|
268
|
+
lines.push("CONTINUITY LEDGER — machine-extracted from the chapters already written. These are facts of the book, not suggestions.");
|
|
269
|
+
if (previous && toggles.enableFM02ContinueFrom) {
|
|
270
|
+
lines.push(`- Previous chapter written: ${previous.chapter}. "${previous.title}".`);
|
|
271
|
+
lines.push(`- The last sentence of the book so far is: "${previous.closingLine}"`);
|
|
272
|
+
lines.push("- THIS CHAPTER CONTINUES FROM THAT EXACT MOMENT. Do not restart the book, do not re-stage the call to adventure, do not write an alternative version of an earlier chapter, and do not re-introduce characters who are already on stage.");
|
|
273
|
+
}
|
|
274
|
+
if (ledger.irreversibleAll.length && toggles.enableFM16IrreversibleLedger) {
|
|
275
|
+
lines.push("- Irreversible events already on the page (they stay true; nothing here may be undone, resurrected, refunded or forgotten):");
|
|
276
|
+
for (const entry of ledger.irreversibleAll.slice(-8)) lines.push(` · ${entry}`);
|
|
277
|
+
}
|
|
278
|
+
if ((ledger.lastClock || ledger.lastDate) && toggles.enableFM13ClockMonotonicity) {
|
|
279
|
+
lines.push(`- Story clock reached: ${[ledger.lastDate, ledger.lastClock].filter(Boolean).join(" ")}. This chapter must move the clock FORWARD by an interval appropriate to the travel and work it depicts, and must never occupy a time earlier than that.`);
|
|
280
|
+
}
|
|
281
|
+
if (recentPlaces.length && toggles.enableFM10TransitRules) {
|
|
282
|
+
lines.push(`- Locations already used in the last chapters: ${recentPlaces.join("; ")}. Do not return to any of them unless the return is itself the dramatic event, and never cut to a new location without one concrete line of transit (how long, by what means, at what cost).`);
|
|
283
|
+
}
|
|
284
|
+
if (liveConcepts.length && toggles.enableFM11ConceptBudget) {
|
|
285
|
+
lines.push(`- Worldbuilding concepts already introduced: ${liveConcepts.join("; ")}. Use them; do not re-explain them.`);
|
|
286
|
+
}
|
|
287
|
+
lines.push("");
|
|
288
|
+
|
|
289
|
+
if (toggles.enableFM09ActLadder) {
|
|
290
|
+
lines.push(`STRUCTURAL OBLIGATION — ${ladder.act} (chapter ${chapterIndex + 1} of ${total}).`);
|
|
291
|
+
lines.push(`- ${ladder.duty}`);
|
|
292
|
+
}
|
|
293
|
+
if (toggles.enableFM03AntiStasis) {
|
|
294
|
+
lines.push("- This chapter must change at least TWO of: physical location, who is present, what the protagonist knows, what the protagonist owns or controls, and the protagonist's legal or social standing. Changing none of them is a failed chapter.");
|
|
295
|
+
if (previous?.dominantVerbs.length) {
|
|
296
|
+
lines.push(`- The previous chapter's dominant activity was: ${previous.dominantVerbs.slice(0, 4).join(", ")}. This chapter may not repeat that activity as its spine.`);
|
|
297
|
+
}
|
|
298
|
+
lines.push("- An inspection, audit, measurement, filing, injunction, deadline or fee may appear only as texture. It may not be the chapter's central action, and it may not be the way the chapter's tension resolves.");
|
|
299
|
+
lines.push("");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
lines.push("CRAFT BUDGETS FOR THIS CHAPTER (hard caps; exceeding them is a defect, not a flourish):");
|
|
303
|
+
if (toggles.enableFM14MeasurementBudget) {
|
|
304
|
+
lines.push(`- At most ${budgets.measurements} numeric measurements with units in the whole chapter. Concrete specificity means named objects, textures, procedures and consequences, not instrument readings.`);
|
|
305
|
+
}
|
|
306
|
+
if (toggles.enableFM15CurrencyBudget) {
|
|
307
|
+
lines.push(`- At most ${budgets.currency} explicit prices, fines, fees or ledger balances.`);
|
|
308
|
+
}
|
|
309
|
+
if (toggles.enableFM11ConceptBudget) {
|
|
310
|
+
lines.push(`- At most ${budgets.newConcepts} NEW named worldbuilding concepts. Dramatize them; do not let characters lecture each other about them.`);
|
|
311
|
+
}
|
|
312
|
+
if (overusedMotifs.length && toggles.enableFM05MotifCooldown) {
|
|
313
|
+
lines.push("- These exact phrases have already been stated on the page and are now BANNED as literal text. Their content must be shown through behaviour, choice and consequence instead:");
|
|
314
|
+
for (const phrase of overusedMotifs) lines.push(` · "${phrase}"`);
|
|
315
|
+
}
|
|
316
|
+
if (toggles.enableFM08PovLock) {
|
|
317
|
+
lines.push(`- Point of view is locked to ${povRule.label}. Do not drift.`);
|
|
318
|
+
if (previous && previous.povViolations > 0) {
|
|
319
|
+
lines.push(` · The previous chapter contained ${previous.povViolations} pronoun(s) outside that point of view. Do not repeat that error.`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (toggles.enableFM07ResidueStripping) {
|
|
323
|
+
lines.push("- Never emit bracketed metadata of any kind: no [Source 1], no [CHECKSUM], no [UNVERIFIED], no status tags, no scratchpad, no planning notes. Narrative prose only.");
|
|
324
|
+
}
|
|
325
|
+
lines.push("- Never quote the story bible back to the reader. Terms such as the dramatic question, the wound, the want and the need are private planning language; if a character says them aloud, the chapter is wrong.");
|
|
326
|
+
lines.push("");
|
|
327
|
+
lines.push("Write ONLY the chapter prose now.");
|
|
328
|
+
return lines.join("\n");
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** Deterministic chapter-level defect report used for logging and warnings. */
|
|
332
|
+
export function auditChapterCraft(facts: ChapterFacts, budgets: CraftBudgets = DEFAULT_BUDGETS): string[] {
|
|
333
|
+
const defects: string[] = [];
|
|
334
|
+
if (facts.measurements.length > budgets.measurements) defects.push(`measurement budget exceeded (${facts.measurements.length}/${budgets.measurements})`);
|
|
335
|
+
if (facts.currency.length > budgets.currency) defects.push(`currency budget exceeded (${facts.currency.length}/${budgets.currency})`);
|
|
336
|
+
if (facts.residue.length) defects.push(`harness residue removed: ${facts.residue.join(", ")}`);
|
|
337
|
+
if (facts.povViolations > 0) defects.push(`${facts.povViolations} pronoun(s) outside the locked point of view`);
|
|
338
|
+
const repeated = Object.entries(facts.motifHits).filter(([, count]) => count > 1).map(([phrase]) => phrase);
|
|
339
|
+
if (repeated.length) defects.push(`bible phrase repeated verbatim in-chapter: ${repeated.slice(0, 3).join("; ")}`);
|
|
340
|
+
return defects;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** FM-04: is the returned chapter truncated or far under its commissioned length? */
|
|
344
|
+
export function truncationVerdict(text: string, targetWords: number): { truncated: boolean; reason: string; words: number } {
|
|
345
|
+
const words = (text.trim().match(/\S+/g) ?? []).length;
|
|
346
|
+
const trimmed = text.trim();
|
|
347
|
+
const unterminated = !!trimmed && !/[.!?"'”’)\]]$/.test(trimmed);
|
|
348
|
+
if (unterminated) return { truncated: true, reason: "final sentence is unterminated", words };
|
|
349
|
+
if (targetWords > 0 && words < Math.floor(targetWords * 0.6)) return { truncated: true, reason: `only ${words} words against a ${targetWords}-word commission`, words };
|
|
350
|
+
return { truncated: false, reason: "", words };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** Continuation request for a truncated chapter. Additive; never rewrites what already landed. */
|
|
354
|
+
export function buildContinuationPrompt(args: {
|
|
355
|
+
snapshot: StorySnapshot; chapterIndex: number; title: string; sofar: string; targetWords: number;
|
|
356
|
+
}): string {
|
|
357
|
+
const words = (args.sofar.trim().match(/\S+/g) ?? []).length;
|
|
358
|
+
return [
|
|
359
|
+
"You are continuing ONE chapter of a work of FICTION that was cut off mid-delivery.",
|
|
360
|
+
"Output ONLY the continuation prose. Do not repeat any sentence that already exists. Do not restate the chapter heading. Do not summarise. Do not start a new chapter.",
|
|
361
|
+
"",
|
|
362
|
+
`CHAPTER: "${args.title}" (chapter ${args.chapterIndex + 1} of ${args.snapshot.blueprint.chapters.length}).`,
|
|
363
|
+
`Commissioned length ~${args.targetWords} words; ${words} words exist so far, so write approximately ${Math.max(200, args.targetWords - words)} more and bring the chapter to a clean, deliberate close.`,
|
|
364
|
+
`Point of view is locked to ${povProfile(args.snapshot.blueprint.pov).label}.`,
|
|
365
|
+
"No bracketed metadata, no planning notes, no instrument-reading padding.",
|
|
366
|
+
"",
|
|
367
|
+
"THE CHAPTER SO FAR (continue seamlessly from its final words):",
|
|
368
|
+
args.sofar.slice(-6000),
|
|
369
|
+
"",
|
|
370
|
+
"Continue the chapter now.",
|
|
371
|
+
].join("\n");
|
|
372
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SAGA OS CRASH PROTECTION & PERSISTENT CHECKPOINT ENGINE
|
|
3
|
+
* ---------------------------------------------------------------------------
|
|
4
|
+
* Ensures state survival across browser tab crashes, disconnects, reloads,
|
|
5
|
+
* and uncaught exceptions.
|
|
6
|
+
*
|
|
7
|
+
* Checkpoint invariants:
|
|
8
|
+
* - Clean chapter narrative stores (no harness preamble or meta-chatter).
|
|
9
|
+
* - Entity state machine serialized to localStorage.
|
|
10
|
+
* - Active status ("idle" | "running" | "paused" | "stopped").
|
|
11
|
+
* - Seamless resumption from exact checkpoint without prompt corruption.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { stripHarnessResidue } from "./continuity";
|
|
15
|
+
|
|
16
|
+
export type StoryEngineStatus = "idle" | "running" | "paused" | "stopped" | "error" | "complete";
|
|
17
|
+
|
|
18
|
+
export interface StoryCheckpoint {
|
|
19
|
+
manuscriptId: string;
|
|
20
|
+
seed: number;
|
|
21
|
+
totalChapters: number;
|
|
22
|
+
completedChapters: number;
|
|
23
|
+
activeModel: string;
|
|
24
|
+
status: StoryEngineStatus;
|
|
25
|
+
timestamp: number;
|
|
26
|
+
chapters: Array<{
|
|
27
|
+
index: number;
|
|
28
|
+
title: string;
|
|
29
|
+
text: string;
|
|
30
|
+
wordCount: number;
|
|
31
|
+
}>;
|
|
32
|
+
cleanManuscript: string;
|
|
33
|
+
entityStateJson?: string;
|
|
34
|
+
ledgerJson?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const CHECKPOINT_KEY = "saga.fiction.checkpoint.v2";
|
|
38
|
+
const ENGINE_STATUS_KEY = "saga.fiction.runtime.status";
|
|
39
|
+
|
|
40
|
+
export class StoryCrashProtector {
|
|
41
|
+
public static saveCheckpoint(checkpoint: StoryCheckpoint): void {
|
|
42
|
+
try {
|
|
43
|
+
// Clean narrative of any residue before serializing
|
|
44
|
+
const sanitizedManuscript = stripHarnessResidue(checkpoint.cleanManuscript).text;
|
|
45
|
+
const payload: StoryCheckpoint = {
|
|
46
|
+
...checkpoint,
|
|
47
|
+
cleanManuscript: sanitizedManuscript,
|
|
48
|
+
timestamp: Date.now(),
|
|
49
|
+
};
|
|
50
|
+
try {
|
|
51
|
+
// Full checkpoint (manuscript + per-chapter array).
|
|
52
|
+
localStorage.setItem(CHECKPOINT_KEY, JSON.stringify(payload));
|
|
53
|
+
} catch (quotaErr) {
|
|
54
|
+
// Quota fallback at max scale: manuscript-only (chapters re-parseable on load).
|
|
55
|
+
try {
|
|
56
|
+
const slim = { ...payload, chapters: [] as StoryCheckpoint["chapters"] };
|
|
57
|
+
localStorage.setItem(CHECKPOINT_KEY, JSON.stringify(slim));
|
|
58
|
+
console.warn("[CrashProtector] Quota hit: saved manuscript-only checkpoint.", quotaErr);
|
|
59
|
+
} catch (e2) {
|
|
60
|
+
console.warn("[CrashProtector] Failed to persist checkpoint (quota exceeded):", e2);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
localStorage.setItem(ENGINE_STATUS_KEY, payload.status);
|
|
65
|
+
window.dispatchEvent(
|
|
66
|
+
new CustomEvent("saga:checkpoint-saved", { detail: { completed: payload.completedChapters, status: payload.status } })
|
|
67
|
+
);
|
|
68
|
+
} catch (e) {
|
|
69
|
+
console.warn("[CrashProtector] Failed to persist checkpoint:", e);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public static loadCheckpoint(): StoryCheckpoint | null {
|
|
74
|
+
try {
|
|
75
|
+
const raw = localStorage.getItem(CHECKPOINT_KEY);
|
|
76
|
+
if (!raw) return null;
|
|
77
|
+
const parsed = JSON.parse(raw) as StoryCheckpoint;
|
|
78
|
+
return parsed;
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public static updateStatus(status: StoryEngineStatus): void {
|
|
85
|
+
try {
|
|
86
|
+
localStorage.setItem(ENGINE_STATUS_KEY, status);
|
|
87
|
+
const cp = this.loadCheckpoint();
|
|
88
|
+
if (cp) {
|
|
89
|
+
cp.status = status;
|
|
90
|
+
localStorage.setItem(CHECKPOINT_KEY, JSON.stringify(cp));
|
|
91
|
+
}
|
|
92
|
+
window.dispatchEvent(new CustomEvent("saga:status-changed", { detail: { status } }));
|
|
93
|
+
} catch {}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public static getActiveStatus(): StoryEngineStatus {
|
|
97
|
+
try {
|
|
98
|
+
return (localStorage.getItem(ENGINE_STATUS_KEY) as StoryEngineStatus) || "idle";
|
|
99
|
+
} catch {
|
|
100
|
+
return "idle";
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public static clearCheckpoint(): void {
|
|
105
|
+
try {
|
|
106
|
+
localStorage.removeItem(CHECKPOINT_KEY);
|
|
107
|
+
localStorage.setItem(ENGINE_STATUS_KEY, "idle");
|
|
108
|
+
window.dispatchEvent(new CustomEvent("saga:checkpoint-cleared"));
|
|
109
|
+
} catch {}
|
|
110
|
+
}
|
|
111
|
+
}
|