@warpgogol/werkstatt-shared 0.2.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/package.json +352 -0
- package/src/checks/index.ts +24 -0
- package/src/checks/lib/astro-site-url.ts +30 -0
- package/src/checks/lib/i18n.ts +30 -0
- package/src/checks/result-helpers.ts +102 -0
- package/src/checks/suppressions-config.ts +195 -0
- package/src/index.ts +14 -0
- package/src/integration/README.md +38 -0
- package/src/integration/adapters.ts +143 -0
- package/src/integration/crm-buffer.ts +491 -0
- package/src/integration/dispatch.ts +106 -0
- package/src/integration/funnel.ts +379 -0
- package/src/integration/index.ts +56 -0
- package/src/integration/lifecycle.ts +155 -0
- package/src/integration/orchestration.ts +439 -0
- package/src/integration/port-barrel.ts +124 -0
- package/src/integration/port.ts +143 -0
- package/src/integration/qstash.ts +134 -0
- package/src/integration/sharding.ts +133 -0
- package/src/integration/tests/deliver-event.test.ts +52 -0
- package/src/integration/tests/dispatch.test.ts +88 -0
- package/src/integration/tests/funnel-bridge.test.ts +46 -0
- package/src/integration/tests/hub-runtime.test.ts +128 -0
- package/src/integration/tests/lifecycle.test.ts +69 -0
- package/src/integration/tests/qstash.test.ts +83 -0
- package/src/integration/tests/sharding.test.ts +64 -0
- package/src/integration/vitest.config.ts +8 -0
- package/src/integration-adapter-supabase-crm/adapter.ts +437 -0
- package/src/integration-adapter-supabase-crm/client.ts +605 -0
- package/src/integration-adapter-supabase-crm/index.ts +28 -0
- package/src/integration-adapter-supabase-crm/pipedrive-sync-target.ts +482 -0
- package/src/integration-adapter-supabase-crm/supabase-rest.ts +89 -0
- package/src/integration-adapter-supabase-crm/tenant-registry.ts +334 -0
- package/src/integration-adapter-supabase-crm/tests/change-balance.pbt.test.ts +83 -0
- package/src/integration-adapter-supabase-crm/tests/funnel-persistence.test.ts +409 -0
- package/src/integration-adapter-supabase-crm/tests/lifecycle-sync.test.ts +288 -0
- package/src/integration-adapter-supabase-crm/tests/stage-map.test.ts +80 -0
- package/src/integration-adapter-supabase-crm/vitest.config.ts +8 -0
- package/src/integration-adapter-supabase-crm/worker.ts +223 -0
- package/src/observability/conventions.ts +93 -0
- package/src/observability/env-resolver.ts +57 -0
- package/src/observability/index.ts +57 -0
- package/src/observability/metric-registry.ts +184 -0
- package/src/observability/otlp-converter.ts +125 -0
- package/src/observability/otlp-json.ts +135 -0
- package/src/observability/otlp-transport.ts +68 -0
- package/src/observability/pusher.ts +142 -0
- package/src/observability/redact.ts +27 -0
- package/src/observability/tests/conventions.test.ts +100 -0
- package/src/observability/tests/metric-registry.test.ts +117 -0
- package/src/observability/tests/otlp-converter.pbt.test.ts +77 -0
- package/src/observability/tests/otlp-converter.test.ts +157 -0
- package/src/observability/tests/otlp-json.test.ts +120 -0
- package/src/observability/tests/pusher.test.ts +75 -0
- package/src/observability/tests/redact.pbt.test.ts +71 -0
- package/src/observability/tests/redact.test.ts +37 -0
- package/src/observability/typed-refs.ts +137 -0
- package/src/observability/vitest.config.ts +8 -0
- package/src/ontology/analytics/matomo-binding.yaml +172 -0
- package/src/ontology/analytics/matomo-fleet.registry.yaml +26 -0
- package/src/ontology/analytics/messkanon.yaml +151 -0
- package/src/ontology/archetype-registry.ts +61 -0
- package/src/ontology/archetypes/components/brand-label.yaml +19 -0
- package/src/ontology/archetypes/components/copyright.yaml +19 -0
- package/src/ontology/archetypes/components/currency-aware-price-display.yaml +19 -0
- package/src/ontology/archetypes/components/currency-selector.yaml +19 -0
- package/src/ontology/archetypes/components/footer-promo.yaml +19 -0
- package/src/ontology/archetypes/components/lang-switcher.yaml +19 -0
- package/src/ontology/archetypes/components/layout.yaml +19 -0
- package/src/ontology/archetypes/components/live-photo.yaml +20 -0
- package/src/ontology/archetypes/components/material-credit.yaml +20 -0
- package/src/ontology/archetypes/components/media.yaml +21 -0
- package/src/ontology/archetypes/components/nachweis-card.yaml +34 -0
- package/src/ontology/archetypes/components/nachweis-detail.yaml +43 -0
- package/src/ontology/archetypes/components/nachweis-list.yaml +37 -0
- package/src/ontology/archetypes/components/nachweis-verify.yaml +31 -0
- package/src/ontology/archetypes/components/not-found.yaml +22 -0
- package/src/ontology/archetypes/components/offer-capacity.yaml +21 -0
- package/src/ontology/archetypes/components/passport-header.yaml +19 -0
- package/src/ontology/archetypes/components/passport-provenance.yaml +20 -0
- package/src/ontology/archetypes/components/passport-score-grid.yaml +20 -0
- package/src/ontology/archetypes/components/passport-star-map.yaml +20 -0
- package/src/ontology/archetypes/components/person-profile.yaml +20 -0
- package/src/ontology/archetypes/components/pulsar.yaml +20 -0
- package/src/ontology/archetypes/components/responsive-image.yaml +19 -0
- package/src/ontology/archetypes/components/scroll-to-top.yaml +22 -0
- package/src/ontology/archetypes/components/social-meta.yaml +20 -0
- package/src/ontology/archetypes/components/structured-data.yaml +19 -0
- package/src/ontology/archetypes/index.json +1003 -0
- package/src/ontology/archetypes/index.yaml +821 -0
- package/src/ontology/archetypes/sections/approach.yaml +23 -0
- package/src/ontology/archetypes/sections/article-list.yaml +30 -0
- package/src/ontology/archetypes/sections/audience-cards.yaml +30 -0
- package/src/ontology/archetypes/sections/breadcrumbs.yaml +21 -0
- package/src/ontology/archetypes/sections/chat-widget.yaml +37 -0
- package/src/ontology/archetypes/sections/comparison-cards.yaml +31 -0
- package/src/ontology/archetypes/sections/controlled-responsibility-block.yaml +34 -0
- package/src/ontology/archetypes/sections/credits.yaml +29 -0
- package/src/ontology/archetypes/sections/donation-card.yaml +53 -0
- package/src/ontology/archetypes/sections/donation-use.yaml +23 -0
- package/src/ontology/archetypes/sections/dynamic-status-block.yaml +35 -0
- package/src/ontology/archetypes/sections/faq-list.yaml +26 -0
- package/src/ontology/archetypes/sections/final-cta.yaml +53 -0
- package/src/ontology/archetypes/sections/hero-decision-card.yaml +65 -0
- package/src/ontology/archetypes/sections/hero.yaml +50 -0
- package/src/ontology/archetypes/sections/impact.yaml +23 -0
- package/src/ontology/archetypes/sections/markdown.yaml +43 -0
- package/src/ontology/archetypes/sections/mountain-journey.yaml +43 -0
- package/src/ontology/archetypes/sections/navigation.yaml +20 -0
- package/src/ontology/archetypes/sections/notausgang-block.yaml +41 -0
- package/src/ontology/archetypes/sections/open-source-registry.yaml +26 -0
- package/src/ontology/archetypes/sections/ownership-block.yaml +57 -0
- package/src/ontology/archetypes/sections/people.yaml +48 -0
- package/src/ontology/archetypes/sections/price-card.yaml +50 -0
- package/src/ontology/archetypes/sections/problem.yaml +23 -0
- package/src/ontology/archetypes/sections/send-message.yaml +63 -0
- package/src/ontology/archetypes/sections/service-metadata-block.yaml +40 -0
- package/src/ontology/archetypes/sections/social-proof.yaml +27 -0
- package/src/ontology/archetypes/sections/transparency.yaml +32 -0
- package/src/ontology/archetypes/sections/trust-strip.yaml +42 -0
- package/src/ontology/archetypes/sections/video-section.yaml +27 -0
- package/src/ontology/archetypes/sections/women.yaml +29 -0
- package/src/ontology/biome-token-projection.ts +229 -0
- package/src/ontology/biomes/check-concrete-blueprint.yaml +118 -0
- package/src/ontology/biomes/handwerk-material-warm.yaml +135 -0
- package/src/ontology/biomes/nonprofit-trust.yaml +123 -0
- package/src/ontology/blueprints/offer.yaml +39 -0
- package/src/ontology/blueprints/prompts/local-market-signal.md +13 -0
- package/src/ontology/blueprints/prompts/website-local-narrative.md +24 -0
- package/src/ontology/blueprints/ratgeber.yaml +62 -0
- package/src/ontology/blueprints/website-local.yaml +309 -0
- package/src/ontology/blueprints/website-service.yaml +84 -0
- package/src/ontology/capabilities/lead.submit.yaml +44 -0
- package/src/ontology/cosmic/index.ts +48 -0
- package/src/ontology/cosmic/moon-catalog.ts +173 -0
- package/src/ontology/cosmic/planet-catalog.ts +82 -0
- package/src/ontology/cosmic/star-catalog.ts +337 -0
- package/src/ontology/enums.ts +159 -0
- package/src/ontology/external-surfaces/index.ts +158 -0
- package/src/ontology/external-surfaces/jsonld-types.yaml +135 -0
- package/src/ontology/external-surfaces/sitemap-shape.yaml +6 -0
- package/src/ontology/external-surfaces/url-schema.yaml +95 -0
- package/src/ontology/growth/events/contact-email-click.yaml +21 -0
- package/src/ontology/growth/events/contact-form-submit.yaml +25 -0
- package/src/ontology/growth/events/contact-phone-click.yaml +21 -0
- package/src/ontology/growth/events/contact-route-click.yaml +21 -0
- package/src/ontology/growth/events/contact-submit.yaml +28 -0
- package/src/ontology/growth/events/contact-whatsapp-click.yaml +21 -0
- package/src/ontology/growth/events/cta-click.yaml +30 -0
- package/src/ontology/growth/events/donation-confirmed.yaml +32 -0
- package/src/ontology/growth/events/donation-form-start.yaml +21 -0
- package/src/ontology/growth/events/donation-form-submit.yaml +29 -0
- package/src/ontology/growth/events/donation-intent.yaml +29 -0
- package/src/ontology/growth/events/form-error.yaml +27 -0
- package/src/ontology/growth/events/form-start.yaml +21 -0
- package/src/ontology/growth/events/form-submit.yaml +21 -0
- package/src/ontology/growth/events/outbound-click.yaml +25 -0
- package/src/ontology/growth/events/page-view.yaml +24 -0
- package/src/ontology/growth/events/passport-view.yaml +28 -0
- package/src/ontology/growth/events/star-map-navigate.yaml +30 -0
- package/src/ontology/index.ts +165 -0
- package/src/ontology/manifest.ts +344 -0
- package/src/ontology/operations/index.ts +1 -0
- package/src/ontology/schemas/biome.ts +303 -0
- package/src/ontology/schemas/capability.ts +104 -0
- package/src/ontology/schemas/constellation.ts +114 -0
- package/src/ontology/schemas/dns-records.ts +53 -0
- package/src/ontology/schemas/index.ts +95 -0
- package/src/ontology/schemas/manifest-resolver.ts +123 -0
- package/src/ontology/schemas/page-entry.ts +213 -0
- package/src/ontology/schemas/section-archetype.ts +109 -0
- package/src/ontology/schemas/site-family.ts +98 -0
- package/src/ontology/schemas/system/growth.ts +80 -0
- package/src/ontology/schemas/system/integrations.ts +143 -0
- package/src/ontology/schemas/system/manifest.ts +492 -0
- package/src/ontology/schemas/system/page-output.ts +99 -0
- package/src/ontology/schemas/system/release.ts +58 -0
- package/src/ontology/schemas/system/text.ts +58 -0
- package/src/ontology/schemas/system.ts +53 -0
- package/src/ontology/shared-section-props/body-fragments.ts +182 -0
- package/src/ontology/shared-section-props/catalog.ts +195 -0
- package/src/ontology/shared-section-props/common.ts +178 -0
- package/src/ontology/shared-section-props/index.ts +34 -0
- package/src/ontology/shared-section-props/visual-header.ts +163 -0
- package/src/ontology/site-families/charity-donation-trust/cultural-rules.yaml +6 -0
- package/src/ontology/site-families/charity-donation-trust/family.yaml +44 -0
- package/src/ontology/site-families/charity-donation-trust/linguistic-rules.yaml +6 -0
- package/src/ontology/site-families/check-quality-operator/cultural-rules.yaml +6 -0
- package/src/ontology/site-families/check-quality-operator/family.yaml +40 -0
- package/src/ontology/site-families/check-quality-operator/linguistic-rules.yaml +6 -0
- package/src/ontology/site-families/handwerk-trust-engineering/cultural-rules.yaml +6 -0
- package/src/ontology/site-families/handwerk-trust-engineering/family.yaml +53 -0
- package/src/ontology/site-families/handwerk-trust-engineering/linguistic-rules.yaml +6 -0
- package/src/ontology/tests/catalogs.pbt.test.ts +89 -0
- package/src/ontology/tests/enums.test.ts +203 -0
- package/src/ontology/tests/sternsystem-owner.test.ts +63 -0
- package/src/ontology/vitest.config.ts +8 -0
- package/src/passport/dht-sign.test.ts +99 -0
- package/src/passport/dht-sign.ts +73 -0
- package/src/passport/identity-sign.test.ts +134 -0
- package/src/passport/identity-sign.ts +85 -0
- package/src/passport/index.ts +53 -0
- package/src/passport/key-rotate.ts +133 -0
- package/src/passport/schema.ts +198 -0
- package/src/passport/sign.ts +225 -0
- package/src/passport/tests/schema.pbt.test.ts +93 -0
- package/src/passport/tests/schema.test.ts +222 -0
- package/src/passport/vitest.config.ts +8 -0
- package/src/share/agent/api-catalog.ts +77 -0
- package/src/share/agent/capability.ts +68 -0
- package/src/share/agent/dns-aid.test.ts +74 -0
- package/src/share/agent/dns-aid.ts +53 -0
- package/src/share/agent/fleet-catalog.ts +149 -0
- package/src/share/agent/index.ts +29 -0
- package/src/share/agent/knowledge.ts +96 -0
- package/src/share/agent/manifest.ts +126 -0
- package/src/share/agent/mcp-card.ts +63 -0
- package/src/share/agent/omit-empty.ts +77 -0
- package/src/share/agent/openapi.ts +157 -0
- package/src/share/agent/proof.ts +61 -0
- package/src/share/attribution-display.ts +86 -0
- package/src/share/check-hints.ts +38 -0
- package/src/share/content/entity-id.ts +65 -0
- package/src/share/content/index.ts +16 -0
- package/src/share/content/merge.ts +86 -0
- package/src/share/content/resolve-field-path.ts +39 -0
- package/src/share/content/substitute-deep.ts +47 -0
- package/src/share/content-discipline/index.ts +14 -0
- package/src/share/content-discipline/parsers.ts +66 -0
- package/src/share/content-discipline/types.ts +118 -0
- package/src/share/content-reference.ts +333 -0
- package/src/share/counter-utils.ts +75 -0
- package/src/share/css-value-normalize.ts +48 -0
- package/src/share/dev-props-validator.ts +131 -0
- package/src/share/entitlement.ts +172 -0
- package/src/share/env.d.ts +73 -0
- package/src/share/feature-policy.ts +367 -0
- package/src/share/formula-eval.ts +364 -0
- package/src/share/fs/index.ts +96 -0
- package/src/share/i18n/localization.ts +57 -0
- package/src/share/image-provider.ts +314 -0
- package/src/share/index.ts +86 -0
- package/src/share/knowledge/claim.ts +129 -0
- package/src/share/knowledge/derivation.ts +55 -0
- package/src/share/knowledge/freshness.ts +127 -0
- package/src/share/knowledge/index.ts +23 -0
- package/src/share/knowledge/ledger.ts +222 -0
- package/src/share/knowledge/plan.ts +122 -0
- package/src/share/knowledge/source.ts +170 -0
- package/src/share/legal/translation-policy.ts +133 -0
- package/src/share/material-credits.ts +181 -0
- package/src/share/middleware/language-redirect.ts +49 -0
- package/src/share/offer-capacity.ts +149 -0
- package/src/share/onboarding-yaml/index.ts +162 -0
- package/src/share/page.ts +351 -0
- package/src/share/redirects.test.ts +62 -0
- package/src/share/redirects.ts +54 -0
- package/src/share/rfc0042-utils.ts +95 -0
- package/src/share/runtime-context.ts +70 -0
- package/src/share/schemas/claim-records.ts +69 -0
- package/src/share/schemas/claims.ts +150 -0
- package/src/share/schemas/effects.ts +212 -0
- package/src/share/schemas/features.ts +147 -0
- package/src/share/schemas/horizontal-align.ts +22 -0
- package/src/share/schemas/icon-color.ts +27 -0
- package/src/share/schemas/index.ts +43 -0
- package/src/share/schemas/live-photo.ts +53 -0
- package/src/share/schemas/material-credit.ts +292 -0
- package/src/share/schemas/media.ts +220 -0
- package/src/share/schemas/navigation.ts +74 -0
- package/src/share/schemas/page-base.ts +33 -0
- package/src/share/schemas/participant.ts +247 -0
- package/src/share/schemas/person.ts +24 -0
- package/src/share/schemas/print.ts +93 -0
- package/src/share/schemas/section-background.ts +106 -0
- package/src/share/schemas/section-body.ts +138 -0
- package/src/share/schemas/section-cards.ts +37 -0
- package/src/share/schemas/section-cta.ts +74 -0
- package/src/share/schemas/section-header.ts +77 -0
- package/src/share/schemas/section-image.ts +77 -0
- package/src/share/schemas/section-motion.ts +52 -0
- package/src/share/schemas/section-shell.ts +63 -0
- package/src/share/schemas/section-stats.ts +37 -0
- package/src/share/schemas/site-background.ts +78 -0
- package/src/share/schemas/standard-list-item.ts +38 -0
- package/src/share/scripts/external-links.ts +45 -0
- package/src/share/scripts/gsap-counter.ts +168 -0
- package/src/share/scripts/gsap-mountain-journey.ts +236 -0
- package/src/share/scripts/gsap-parallax.ts +50 -0
- package/src/share/scripts/gsap-reveal.ts +65 -0
- package/src/share/scripts/gsap-stagger.ts +54 -0
- package/src/share/scripts/index.ts +25 -0
- package/src/share/scripts/inline-number-animation.ts +120 -0
- package/src/share/scripts/lenis.ts +109 -0
- package/src/share/scripts/live-photos.ts +250 -0
- package/src/share/scripts/lordicon.ts +94 -0
- package/src/share/scripts/orchestrator.ts +138 -0
- package/src/share/scripts/scheduler.ts +53 -0
- package/src/share/scripts/video-player.ts +116 -0
- package/src/share/semantic/ai.ts +140 -0
- package/src/share/semantic/block-extraction.ts +52 -0
- package/src/share/semantic/block-extractors/index.ts +502 -0
- package/src/share/semantic/breadcrumbs.ts +120 -0
- package/src/share/semantic/build-page.ts +309 -0
- package/src/share/semantic/business-projection.ts +278 -0
- package/src/share/semantic/derived-prices-loader.ts +36 -0
- package/src/share/semantic/extract.ts +118 -0
- package/src/share/semantic/feed.ts +124 -0
- package/src/share/semantic/ids.ts +76 -0
- package/src/share/semantic/image-sitemap.ts +153 -0
- package/src/share/semantic/index.ts +36 -0
- package/src/share/semantic/jsonld/article.ts +47 -0
- package/src/share/semantic/jsonld/breadcrumb.ts +47 -0
- package/src/share/semantic/jsonld/collection-list.ts +30 -0
- package/src/share/semantic/jsonld/context.ts +46 -0
- package/src/share/semantic/jsonld/faq.ts +49 -0
- package/src/share/semantic/jsonld/initiative.ts +53 -0
- package/src/share/semantic/jsonld/organization.ts +101 -0
- package/src/share/semantic/jsonld/person.ts +71 -0
- package/src/share/semantic/jsonld/service.ts +80 -0
- package/src/share/semantic/jsonld/shared.ts +31 -0
- package/src/share/semantic/jsonld/types.ts +18 -0
- package/src/share/semantic/jsonld/webpage.ts +98 -0
- package/src/share/semantic/jsonld/website.ts +27 -0
- package/src/share/semantic/jsonld.ts +75 -0
- package/src/share/semantic/llms-policy.ts +74 -0
- package/src/share/semantic/llms.ts +394 -0
- package/src/share/semantic/markdown-hygiene.ts +68 -0
- package/src/share/semantic/markdown-twin-provenance.ts +242 -0
- package/src/share/semantic/models.ts +402 -0
- package/src/share/semantic/organization-profile.ts +160 -0
- package/src/share/semantic/output-projection.ts +131 -0
- package/src/share/semantic/page-builders/markdown-page.ts +84 -0
- package/src/share/semantic/page-markdown.ts +234 -0
- package/src/share/semantic/page-utils.ts +125 -0
- package/src/share/semantic/price-marker-resolver.ts +92 -0
- package/src/share/semantic/robots.ts +127 -0
- package/src/share/semantic/update-stamp.ts +136 -0
- package/src/share/shared-context.ts +177 -0
- package/src/share/string-utils.ts +19 -0
- package/src/share/tests/api-catalog.test.ts +97 -0
- package/src/share/tests/breadcrumbs.test.ts +126 -0
- package/src/share/tests/build-page-price-markers.test.ts +160 -0
- package/src/share/tests/capability.test.ts +107 -0
- package/src/share/tests/claim.test.ts +90 -0
- package/src/share/tests/content-ref-index.test.ts +328 -0
- package/src/share/tests/create-dev-props-validator.test.ts +38 -0
- package/src/share/tests/derivation.test.ts +39 -0
- package/src/share/tests/dev-props-validator.test.ts +88 -0
- package/src/share/tests/fleet-catalog.test.ts +134 -0
- package/src/share/tests/formula-eval.test.ts +619 -0
- package/src/share/tests/freshness.test.ts +60 -0
- package/src/share/tests/hero-section-numbering-parity.test.ts +51 -0
- package/src/share/tests/image-sitemap.test.ts +101 -0
- package/src/share/tests/jsonld-service.test.ts +282 -0
- package/src/share/tests/knowledge.test.ts +86 -0
- package/src/share/tests/ledger.test.ts +125 -0
- package/src/share/tests/llms-0789.test.ts +63 -0
- package/src/share/tests/manifest.test.ts +100 -0
- package/src/share/tests/markdown-twin-provenance.test.ts +108 -0
- package/src/share/tests/mcp-card.test.ts +57 -0
- package/src/share/tests/onboarding-yaml.test.ts +111 -0
- package/src/share/tests/ontology-role-by-cosmic-name.test.ts +28 -0
- package/src/share/tests/openapi.test.ts +116 -0
- package/src/share/tests/organization-jsonld.test.ts +74 -0
- package/src/share/tests/output-projection.test.ts +61 -0
- package/src/share/tests/plan.test.ts +97 -0
- package/src/share/tests/price-marker-resolver.test.ts +141 -0
- package/src/share/tests/proof.test.ts +72 -0
- package/src/share/tests/resolve-field-path.test.ts +52 -0
- package/src/share/tests/source.test.ts +81 -0
- package/src/share/tests/substitute-deep.test.ts +65 -0
- package/src/share/tests/text-normalize.pbt.test.ts +143 -0
- package/src/share/tests/text-normalize.test.ts +285 -0
- package/src/share/tests/twin-path.test.ts +43 -0
- package/src/share/text-normalize.ts +548 -0
- package/src/share/text-position.ts +19 -0
- package/src/share/types/hls-js-light.d.ts +16 -0
- package/src/share/visibility.ts +167 -0
- package/src/share/vitest.config.ts +8 -0
- package/src/share/wrap-inline-numbers.ts +139 -0
- package/src/surface/blueprint-schema.ts +314 -0
- package/src/surface/blueprint-types.ts +256 -0
- package/src/surface/blueprint.ts +300 -0
- package/src/surface/breaker.ts +57 -0
- package/src/surface/decision-composer.ts +156 -0
- package/src/surface/eligibility.ts +372 -0
- package/src/surface/evidence-records.ts +97 -0
- package/src/surface/fleet.ts +60 -0
- package/src/surface/geo.ts +170 -0
- package/src/surface/governance.ts +146 -0
- package/src/surface/index.ts +216 -0
- package/src/surface/io/index.ts +18 -0
- package/src/surface/io/surface-module-context-io.ts +60 -0
- package/src/surface/io/visibility-outcomes-io.ts +46 -0
- package/src/surface/module-context.ts +120 -0
- package/src/surface/substance.ts +161 -0
- package/src/surface/tests/eligibility.pbt.test.ts +137 -0
- package/src/surface/tests/eligibility.test.ts +302 -0
- package/src/surface/types.ts +289 -0
- package/src/surface/visibility.ts +57 -0
- package/src/surface/vitest.config.ts +8 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>RFC-0148: the single, framework-agnostic organization-profile assembler. The mapping from business-schema fields to SemanticOrganization lives here once; the disk llms path and the Astro JSON-LD path both resolve their own data (each reads + substitutes content references in its own way) and delegate the assembly here. Replaces the two duplicated org builders (no legacy).</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not read files or substitute content references — callers pass resolved values.</item>
|
|
6
|
+
<item>Do not iterate pages — this builds the site-wide profile only.</item>
|
|
7
|
+
</non-goals>
|
|
8
|
+
</MODULE_CONTRACT>
|
|
9
|
+
<CHANGE_SUMMARY>
|
|
10
|
+
<item>RFC-0148: extracted the shared org-profile assembler from the disk + Astro builders.</item>
|
|
11
|
+
</CHANGE_SUMMARY>
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { getBaseUrl, toAbsoluteUrl } from "./ids.ts";
|
|
15
|
+
import type {
|
|
16
|
+
SemanticInitiative,
|
|
17
|
+
SemanticLocation,
|
|
18
|
+
SemanticOffer,
|
|
19
|
+
SemanticOrganization,
|
|
20
|
+
SemanticPerson,
|
|
21
|
+
SemanticService,
|
|
22
|
+
} from "./models.ts";
|
|
23
|
+
|
|
24
|
+
export type SemanticSiteProfile = {
|
|
25
|
+
organization: SemanticOrganization;
|
|
26
|
+
people: SemanticPerson[];
|
|
27
|
+
initiatives: SemanticInitiative[];
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Resolved owner address primitives (content references already substituted). */
|
|
31
|
+
export interface ProfileAddressInput {
|
|
32
|
+
street?: string;
|
|
33
|
+
streetNumber?: string;
|
|
34
|
+
zip?: string;
|
|
35
|
+
city?: string;
|
|
36
|
+
country?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Resolved bank primitives (accountHolder already substituted). */
|
|
40
|
+
export interface ProfileBankInput {
|
|
41
|
+
accountHolder?: string;
|
|
42
|
+
iban?: string;
|
|
43
|
+
bic?: string;
|
|
44
|
+
bankName?: string;
|
|
45
|
+
konto?: string;
|
|
46
|
+
blz?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Normalized, fully-resolved business primitives. Each caller (disk fs reader,
|
|
51
|
+
* Astro content-layer reader) maps its source into this shape — including any
|
|
52
|
+
* RFC-0045 content-reference substitution — so the assembly below is identical
|
|
53
|
+
* regardless of source.
|
|
54
|
+
*/
|
|
55
|
+
export interface OrganizationProfileInput {
|
|
56
|
+
lang: string;
|
|
57
|
+
siteUrl: URL | string;
|
|
58
|
+
/** Resolved brand name. */
|
|
59
|
+
brandName: string;
|
|
60
|
+
description: string;
|
|
61
|
+
foundingYear?: string;
|
|
62
|
+
areaServed?: string[];
|
|
63
|
+
founders: SemanticPerson[];
|
|
64
|
+
boardMembers: SemanticPerson[];
|
|
65
|
+
legalName?: string;
|
|
66
|
+
registration?: string;
|
|
67
|
+
representativeName?: string;
|
|
68
|
+
address?: ProfileAddressInput;
|
|
69
|
+
/** Present when a bank block exists (drives donationAccount emission). */
|
|
70
|
+
bank?: ProfileBankInput;
|
|
71
|
+
email?: string;
|
|
72
|
+
contactType?: string;
|
|
73
|
+
offer?: SemanticOffer;
|
|
74
|
+
location?: SemanticLocation;
|
|
75
|
+
team?: SemanticPerson[];
|
|
76
|
+
/* RFC-0163: social/profile URLs → Organization.sameAs. */
|
|
77
|
+
sameAs?: string[];
|
|
78
|
+
/* RFC-0163: absolute logo URL → Organization.logo. */
|
|
79
|
+
logoUrl?: string;
|
|
80
|
+
/** RFC-0373: projected business service catalog. */
|
|
81
|
+
services?: SemanticService[];
|
|
82
|
+
/** RFC-0242: explicit schema.org @type override (e.g. Bodenstation → Organization/ProfessionalService). */
|
|
83
|
+
schemaType?: string[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** RFC-0148: assemble the site profile from resolved business primitives. */
|
|
87
|
+
export function buildOrganizationProfile(input: OrganizationProfileInput): SemanticSiteProfile {
|
|
88
|
+
const baseUrl = getBaseUrl(input.siteUrl);
|
|
89
|
+
// Use the full team when available (includes founders, board, team, patron, etc.),
|
|
90
|
+
// otherwise fall back to founders + boardMembers. Deduplicate by name.
|
|
91
|
+
const rawPeople = input.team?.length
|
|
92
|
+
? [...input.team]
|
|
93
|
+
: [...input.founders, ...input.boardMembers];
|
|
94
|
+
const seen = new Set<string>();
|
|
95
|
+
const people: SemanticPerson[] = [];
|
|
96
|
+
for (const p of rawPeople) {
|
|
97
|
+
if (!seen.has(p.name)) {
|
|
98
|
+
seen.add(p.name);
|
|
99
|
+
people.push(p);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const organization: SemanticOrganization = {
|
|
104
|
+
name: input.brandName,
|
|
105
|
+
legalName: input.legalName,
|
|
106
|
+
description: input.description,
|
|
107
|
+
url: toAbsoluteUrl(baseUrl, `/${input.lang}/`),
|
|
108
|
+
foundingYear: input.foundingYear,
|
|
109
|
+
email: input.email,
|
|
110
|
+
registration: input.registration,
|
|
111
|
+
representative: input.representativeName,
|
|
112
|
+
...(input.address
|
|
113
|
+
? {
|
|
114
|
+
address: {
|
|
115
|
+
streetAddress: `${input.address.street} ${input.address.streetNumber}`,
|
|
116
|
+
postalCode: input.address.zip,
|
|
117
|
+
addressLocality: input.address.city,
|
|
118
|
+
addressCountry: input.address.country,
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
: {}),
|
|
122
|
+
...(input.areaServed?.length ? { areaServed: input.areaServed } : {}),
|
|
123
|
+
...(input.founders.length ? { founders: input.founders } : {}),
|
|
124
|
+
...(input.boardMembers.length ? { boardMembers: input.boardMembers } : {}),
|
|
125
|
+
...(input.contactType || input.email
|
|
126
|
+
? {
|
|
127
|
+
contactPoints: [
|
|
128
|
+
{
|
|
129
|
+
contactType: input.contactType ?? "",
|
|
130
|
+
email: input.email,
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
}
|
|
134
|
+
: {}),
|
|
135
|
+
...(input.bank
|
|
136
|
+
? {
|
|
137
|
+
donationAccount: {
|
|
138
|
+
accountHolder: input.bank.accountHolder ?? "",
|
|
139
|
+
iban: input.bank.iban,
|
|
140
|
+
bic: input.bank.bic,
|
|
141
|
+
bankName: input.bank.bankName,
|
|
142
|
+
accountNumber: input.bank.konto,
|
|
143
|
+
bankCode: input.bank.blz,
|
|
144
|
+
},
|
|
145
|
+
}
|
|
146
|
+
: {}),
|
|
147
|
+
...(input.offer ? { offer: input.offer } : {}),
|
|
148
|
+
...(input.location ? { location: input.location } : {}),
|
|
149
|
+
...(input.team?.length ? { team: input.team } : {}),
|
|
150
|
+
// RFC-0163: entity-grounding signals. `image` always points at the guaranteed
|
|
151
|
+
// /og-image.png fallback (a real, present asset); sameAs/logo emit only when authored.
|
|
152
|
+
image: toAbsoluteUrl(baseUrl, "/og-image.png"),
|
|
153
|
+
...(input.sameAs?.length ? { sameAs: input.sameAs } : {}),
|
|
154
|
+
...(input.logoUrl ? { logo: input.logoUrl } : {}),
|
|
155
|
+
...(input.schemaType?.length ? { schemaType: input.schemaType } : {}),
|
|
156
|
+
...(input.services?.length ? { services: input.services } : {}),
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
return { organization, people, initiatives: [] };
|
|
160
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>RFC-0143: pure resolution of the per-page `output` projection container. Single resolution point shared by every per-page output generator (sitemap, llms) so the static disk loader, the Astro runtime path, and the sitemap command cannot drift.</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not read files — pure function only.</item>
|
|
6
|
+
<item>Do not own site-wide policy blocks (ai:, robots:) — those are separate top-level surfaces.</item>
|
|
7
|
+
<item>Do not own per-generator value semantics beyond defaulting (llms depth lives in llms-policy.ts).</item>
|
|
8
|
+
</non-goals>
|
|
9
|
+
</MODULE_CONTRACT>
|
|
10
|
+
<CHANGE_SUMMARY>
|
|
11
|
+
<item>RFC-0143: Initial implementation. Folds legacy sitemapExclude + per-page sitemap block into output.sitemap.</item>
|
|
12
|
+
<item>RFC-0328: Apply legal-page default sitemap category when semanticType is "legal".</item>
|
|
13
|
+
<item>RFC-0143 cleanup: Removed deprecated sitemapExclude and top-level sitemap alias handling.</item>
|
|
14
|
+
</CHANGE_SUMMARY>
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type {
|
|
18
|
+
PageOutputProjection,
|
|
19
|
+
RobotsProjection,
|
|
20
|
+
SemanticImage,
|
|
21
|
+
SitemapProjection,
|
|
22
|
+
} from "./models.ts";
|
|
23
|
+
import { resolveLlmsPolicy, type RawLlmsPolicy } from "./llms-policy.ts";
|
|
24
|
+
|
|
25
|
+
/** Raw `output.image` as authored in system.md. RFC-0165/0167. */
|
|
26
|
+
export type RawImageProjection = {
|
|
27
|
+
url: string;
|
|
28
|
+
alt?: string;
|
|
29
|
+
width?: number;
|
|
30
|
+
height?: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Resolve the author-declared per-page content image. Returns a SemanticImage
|
|
35
|
+
* tagged `contentImage: true` (eligible for the image sitemap), or undefined
|
|
36
|
+
* when no image is declared or the url is empty.
|
|
37
|
+
*/
|
|
38
|
+
function resolveImage(raw: RawImageProjection | undefined): SemanticImage | undefined {
|
|
39
|
+
if (!raw || typeof raw.url !== "string" || raw.url.trim() === "") return undefined;
|
|
40
|
+
return {
|
|
41
|
+
url: raw.url,
|
|
42
|
+
...(raw.alt !== undefined ? { alt: raw.alt } : {}),
|
|
43
|
+
...(raw.width !== undefined ? { width: raw.width } : {}),
|
|
44
|
+
...(raw.height !== undefined ? { height: raw.height } : {}),
|
|
45
|
+
contentImage: true,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Raw `output.robots` as authored in system.md. RFC-0165. */
|
|
50
|
+
export type RawRobotsProjection =
|
|
51
|
+
| boolean
|
|
52
|
+
| {
|
|
53
|
+
index?: boolean;
|
|
54
|
+
follow?: boolean;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function resolveRobots(raw: RawRobotsProjection | undefined): RobotsProjection {
|
|
58
|
+
if (typeof raw === "boolean") {
|
|
59
|
+
// `output.robots: false` is shorthand for noindex,nofollow.
|
|
60
|
+
return { index: raw, follow: raw };
|
|
61
|
+
}
|
|
62
|
+
const obj = raw ?? {};
|
|
63
|
+
return { index: obj.index ?? true, follow: obj.follow ?? true };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Raw `output.sitemap` as authored in system.md (boolean shorthand or object). */
|
|
67
|
+
export type RawSitemapProjection =
|
|
68
|
+
| boolean
|
|
69
|
+
| {
|
|
70
|
+
include?: boolean;
|
|
71
|
+
category?: string;
|
|
72
|
+
lastmod?: string;
|
|
73
|
+
includeLastmod?: boolean;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** Raw `output` block as authored in system.md. Closed: only known generator keys. */
|
|
77
|
+
export type RawPageOutput = {
|
|
78
|
+
sitemap?: RawSitemapProjection;
|
|
79
|
+
llms?: RawLlmsPolicy;
|
|
80
|
+
/** RFC-0165: per-page indexability. */
|
|
81
|
+
robots?: RawRobotsProjection;
|
|
82
|
+
/** RFC-0165/0167: author-declared content illustration (absolute https url). */
|
|
83
|
+
image?: RawImageProjection;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export interface ResolvePageOutputOptions {
|
|
87
|
+
semanticType?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const DEFAULT_SITEMAP_CATEGORY = "content";
|
|
91
|
+
|
|
92
|
+
function resolveSitemap(
|
|
93
|
+
raw: RawSitemapProjection | undefined,
|
|
94
|
+
defaultCategory: string = DEFAULT_SITEMAP_CATEGORY,
|
|
95
|
+
): SitemapProjection {
|
|
96
|
+
// Boolean shorthand: `output.sitemap: false`
|
|
97
|
+
if (typeof raw === "boolean") {
|
|
98
|
+
return {
|
|
99
|
+
include: raw,
|
|
100
|
+
category: defaultCategory,
|
|
101
|
+
lastmod: undefined,
|
|
102
|
+
includeLastmod: true,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const obj = raw ?? {};
|
|
107
|
+
return {
|
|
108
|
+
include: obj.include ?? true,
|
|
109
|
+
category: obj.category ?? defaultCategory,
|
|
110
|
+
lastmod: obj.lastmod,
|
|
111
|
+
includeLastmod: obj.includeLastmod ?? true,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* RFC-0143: resolve every per-page projection from the raw `output` block.
|
|
117
|
+
* The single resolution point for all per-page output generators.
|
|
118
|
+
*/
|
|
119
|
+
export function resolvePageOutput(
|
|
120
|
+
raw: RawPageOutput | undefined,
|
|
121
|
+
options: ResolvePageOutputOptions = {},
|
|
122
|
+
): PageOutputProjection {
|
|
123
|
+
const output = raw ?? {};
|
|
124
|
+
const defaultCategory = options.semanticType === "legal" ? "legal" : DEFAULT_SITEMAP_CATEGORY;
|
|
125
|
+
return {
|
|
126
|
+
sitemap: resolveSitemap(output.sitemap, defaultCategory),
|
|
127
|
+
llms: resolveLlmsPolicy(output.llms, options.semanticType ?? ""),
|
|
128
|
+
robots: resolveRobots(output.robots),
|
|
129
|
+
...(resolveImage(output.image) ? { image: resolveImage(output.image) } : {}),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Generic semantic builder for markdown-based pages (impressum, datenschutz, agb, widerruf, open-source, etc.). Replaces 7 copy-paste page builders.</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not handle home page or other specialized page types.</item>
|
|
6
|
+
<item>Do not contain app-specific extractor logic.</item>
|
|
7
|
+
</non-goals>
|
|
8
|
+
</MODULE_CONTRACT>
|
|
9
|
+
<CHANGE_SUMMARY>
|
|
10
|
+
<item>Consolidated 7 copy-paste page builders (impressum, datenschutz, agb, widerruf, open-source, projekte, wir-ueber-uns) into one generic function.</item>
|
|
11
|
+
<item>Introduced app-agnostic markdown-page semantic builder and breadcrumb normalization.</item>
|
|
12
|
+
<item>Unified blocksToMarkdown signature — eliminated duplication.</item>
|
|
13
|
+
<item>RFC-0372: MarkdownPageInput now accepts blocks: SemanticBlock[] directly; removed bodyText/contentBlocks/answerBlocks from input and output.</item>
|
|
14
|
+
</CHANGE_SUMMARY>
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { getBaseUrl, toAbsoluteUrl, toCanonicalUrl } from "../ids.ts";
|
|
18
|
+
import type {
|
|
19
|
+
SemanticBlock,
|
|
20
|
+
SemanticBreadcrumb,
|
|
21
|
+
SemanticPageModel,
|
|
22
|
+
SemanticPageType,
|
|
23
|
+
} from "../models.ts";
|
|
24
|
+
|
|
25
|
+
export interface MarkdownPageInput {
|
|
26
|
+
type: SemanticPageType;
|
|
27
|
+
lang: string;
|
|
28
|
+
url: URL | string;
|
|
29
|
+
title: string;
|
|
30
|
+
description: string;
|
|
31
|
+
/** RFC-0377: optional audience override for the page. */
|
|
32
|
+
audience?: string;
|
|
33
|
+
heading: string;
|
|
34
|
+
lead?: string;
|
|
35
|
+
/** RFC-0372: unified block array — replaces bodyText/contentBlocks/answerBlocks. */
|
|
36
|
+
blocks: SemanticBlock[];
|
|
37
|
+
breadcrumbsContent?: { homeLabel: string };
|
|
38
|
+
/**
|
|
39
|
+
* RFC-0229: the canonical breadcrumb trail (Home → live ancestors → self), supplied by the
|
|
40
|
+
* render pipeline so the JSON-LD BreadcrumbList matches the visible section exactly. When omitted
|
|
41
|
+
* the builder falls back to a flat Home → self trail (used by source paths with no ancestor
|
|
42
|
+
* resolver, e.g. the disk-side llms model).
|
|
43
|
+
*/
|
|
44
|
+
breadcrumbs?: SemanticBreadcrumb[];
|
|
45
|
+
people?: Array<{ name: string; role?: string; description?: string; isDeceased?: boolean }>;
|
|
46
|
+
initiatives?: Array<{ id: string; name: string; summary: string; facts?: string[] }>;
|
|
47
|
+
faqEntries?: Array<{
|
|
48
|
+
id: string;
|
|
49
|
+
question: string;
|
|
50
|
+
answer: string;
|
|
51
|
+
tags?: string[];
|
|
52
|
+
serviceSlug?: string;
|
|
53
|
+
}>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function buildMarkdownPageSemantic(input: MarkdownPageInput): SemanticPageModel {
|
|
57
|
+
return {
|
|
58
|
+
type: input.type,
|
|
59
|
+
lang: input.lang,
|
|
60
|
+
url: toCanonicalUrl(input.url),
|
|
61
|
+
title: input.title,
|
|
62
|
+
description: input.description,
|
|
63
|
+
...(input.audience ? { audience: input.audience } : {}),
|
|
64
|
+
heading: input.heading,
|
|
65
|
+
lead: input.lead,
|
|
66
|
+
// RFC-0229: prefer the canonical trail from the render pipeline; otherwise build the flat
|
|
67
|
+
// Home → self fallback (preserves behavior for source paths with no ancestor resolver).
|
|
68
|
+
breadcrumbs:
|
|
69
|
+
input.breadcrumbs && input.breadcrumbs.length > 0
|
|
70
|
+
? input.breadcrumbs
|
|
71
|
+
: [
|
|
72
|
+
{
|
|
73
|
+
name: input.breadcrumbsContent?.homeLabel ?? "Home",
|
|
74
|
+
url: toAbsoluteUrl(getBaseUrl(input.url), `/${input.lang}/`),
|
|
75
|
+
},
|
|
76
|
+
{ name: input.heading, url: toCanonicalUrl(input.url) },
|
|
77
|
+
],
|
|
78
|
+
blocks: input.blocks,
|
|
79
|
+
organization: {} as SemanticPageModel["organization"],
|
|
80
|
+
people: input.people,
|
|
81
|
+
initiatives: input.initiatives,
|
|
82
|
+
faqEntries: input.faqEntries?.length ? input.faqEntries : undefined,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>RFC-0377: pure per-page Markdown projector. Turns a SemanticPageModel into a clean,
|
|
4
|
+
agent-structured Markdown document with a predictable section hierarchy (Summary, Business context,
|
|
5
|
+
Data / APIs, User flows, Constraints) plus optional People, Initiatives, and FAQ. Framework-agnostic
|
|
6
|
+
— no DOM, no fetch, no HTML scraping.</purpose>
|
|
7
|
+
<non-goals>
|
|
8
|
+
<item>Do not convert HTML — content is already structured. HTML→Markdown (CMS richText) lives
|
|
9
|
+
in the content-source adapter, never here.</item>
|
|
10
|
+
</non-goals>
|
|
11
|
+
</MODULE_CONTRACT>
|
|
12
|
+
<CHANGE_SUMMARY>
|
|
13
|
+
<item>RFC-0166: initial implementation — build-time per-page Markdown twin.</item>
|
|
14
|
+
<item>RFC-0372: buildPageMarkdown now renders from unified page.blocks instead of answerBlocks + contentBlocks.</item>
|
|
15
|
+
<item>RFC-0377: restructured body into standardized sections (Summary, Business context, Data / APIs, User flows, Constraints) with heuristic block classification.</item>
|
|
16
|
+
</CHANGE_SUMMARY>
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
canonicalizeGeneratedMarkdownText,
|
|
21
|
+
formatGeneratedMarkdownListItem,
|
|
22
|
+
} from "./markdown-hygiene.ts";
|
|
23
|
+
import type { SemanticBlock, SemanticPageModel } from "./models.ts";
|
|
24
|
+
|
|
25
|
+
type SectionKind = "summary" | "business-context" | "data-apis" | "user-flows" | "constraints";
|
|
26
|
+
|
|
27
|
+
function clean(page: SemanticPageModel, text: string | undefined): string {
|
|
28
|
+
const baseUrl = (() => {
|
|
29
|
+
try {
|
|
30
|
+
return new URL(page.url).origin;
|
|
31
|
+
} catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
})();
|
|
35
|
+
return canonicalizeGeneratedMarkdownText(text, {
|
|
36
|
+
baseUrl,
|
|
37
|
+
defaultLanguage: page.defaultLanguage,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function blockHasContent(block: SemanticBlock): boolean {
|
|
42
|
+
return Boolean(
|
|
43
|
+
block.heading || block.summary || block.body || block.facts?.length || block.items?.length,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function classifyBlock(block: SemanticBlock): Exclude<SectionKind, "summary"> {
|
|
48
|
+
const heading = block.heading.toLowerCase();
|
|
49
|
+
const blockType = (block.blockType ?? "").toLowerCase();
|
|
50
|
+
|
|
51
|
+
const constraintTokens = [
|
|
52
|
+
"legal",
|
|
53
|
+
"recht",
|
|
54
|
+
"datenschutz",
|
|
55
|
+
"privacy",
|
|
56
|
+
"impressum",
|
|
57
|
+
"terms",
|
|
58
|
+
"conditions",
|
|
59
|
+
"constraints",
|
|
60
|
+
"pflicht",
|
|
61
|
+
"liability",
|
|
62
|
+
"haftung",
|
|
63
|
+
];
|
|
64
|
+
if (constraintTokens.some((token) => heading.includes(token) || blockType.includes(token))) {
|
|
65
|
+
return "constraints";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const flowTokens = [
|
|
69
|
+
"flow",
|
|
70
|
+
"how it works",
|
|
71
|
+
"so funktioniert",
|
|
72
|
+
"steps",
|
|
73
|
+
"schritte",
|
|
74
|
+
"process",
|
|
75
|
+
"ablauf",
|
|
76
|
+
"cta",
|
|
77
|
+
"call-to-action",
|
|
78
|
+
];
|
|
79
|
+
if (
|
|
80
|
+
flowTokens.some((token) => heading.includes(token) || blockType.includes(token)) ||
|
|
81
|
+
blockType.includes("hero-decision-card")
|
|
82
|
+
) {
|
|
83
|
+
return "user-flows";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (block.facts?.length || block.items?.length) {
|
|
87
|
+
return "data-apis";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return "business-context";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function extractSummaryText(page: SemanticPageModel): string | undefined {
|
|
94
|
+
if (page.lead) return page.lead;
|
|
95
|
+
if (page.description) return page.description;
|
|
96
|
+
for (const block of page.blocks) {
|
|
97
|
+
if (block.summary) return block.summary;
|
|
98
|
+
if (block.body) return block.body;
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function renderBlock(page: SemanticPageModel, block: SemanticBlock, depth = 3): string[] {
|
|
104
|
+
const lines: string[] = [];
|
|
105
|
+
const prefix = "#".repeat(depth);
|
|
106
|
+
|
|
107
|
+
if (block.heading) {
|
|
108
|
+
lines.push(`${prefix} ${block.heading}`);
|
|
109
|
+
}
|
|
110
|
+
if (block.summary) {
|
|
111
|
+
lines.push("");
|
|
112
|
+
lines.push(clean(page, block.summary));
|
|
113
|
+
}
|
|
114
|
+
if (block.body) {
|
|
115
|
+
lines.push("");
|
|
116
|
+
lines.push(clean(page, block.body));
|
|
117
|
+
}
|
|
118
|
+
if (block.facts?.length) {
|
|
119
|
+
lines.push("");
|
|
120
|
+
for (const fact of block.facts) {
|
|
121
|
+
lines.push(...formatGeneratedMarkdownListItem(clean(page, fact)));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (block.items?.length) {
|
|
125
|
+
lines.push("");
|
|
126
|
+
for (const item of block.items) {
|
|
127
|
+
lines.push(`${"#".repeat(depth + 1)} ${item.title}`);
|
|
128
|
+
if (item.description) {
|
|
129
|
+
lines.push("");
|
|
130
|
+
lines.push(clean(page, item.description));
|
|
131
|
+
}
|
|
132
|
+
lines.push("");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return lines;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function renderSection(
|
|
140
|
+
page: SemanticPageModel,
|
|
141
|
+
heading: string,
|
|
142
|
+
blocks: SemanticBlock[],
|
|
143
|
+
): string[] {
|
|
144
|
+
const lines: string[] = ["", `## ${heading}`, ""];
|
|
145
|
+
for (const block of blocks) {
|
|
146
|
+
lines.push(...renderBlock(page, block));
|
|
147
|
+
}
|
|
148
|
+
return lines;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function buildPageMarkdown(page: SemanticPageModel): string {
|
|
152
|
+
const lines: string[] = [];
|
|
153
|
+
|
|
154
|
+
lines.push(`# ${page.title}`);
|
|
155
|
+
lines.push("");
|
|
156
|
+
|
|
157
|
+
// RFC-0377: Summary section is required. Fallback chain: lead → description → first block summary → first block body.
|
|
158
|
+
const summaryText = extractSummaryText(page);
|
|
159
|
+
if (summaryText) {
|
|
160
|
+
lines.push("## Summary");
|
|
161
|
+
lines.push("");
|
|
162
|
+
lines.push(clean(page, summaryText));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// RFC-0320: the old relative `Source: /path` footer is retired.
|
|
166
|
+
// Provenance now lives in YAML frontmatter added by the generator.
|
|
167
|
+
|
|
168
|
+
// RFC-0377: classify blocks into standardized sections.
|
|
169
|
+
const businessContext: SemanticBlock[] = [];
|
|
170
|
+
const dataApis: SemanticBlock[] = [];
|
|
171
|
+
const userFlows: SemanticBlock[] = [];
|
|
172
|
+
const constraints: SemanticBlock[] = [];
|
|
173
|
+
|
|
174
|
+
for (const block of page.blocks) {
|
|
175
|
+
if (!blockHasContent(block)) continue;
|
|
176
|
+
const section = classifyBlock(block);
|
|
177
|
+
if (section === "business-context") businessContext.push(block);
|
|
178
|
+
if (section === "data-apis") dataApis.push(block);
|
|
179
|
+
if (section === "user-flows") userFlows.push(block);
|
|
180
|
+
if (section === "constraints") constraints.push(block);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
lines.push(...renderSection(page, "Business context", businessContext));
|
|
184
|
+
lines.push(...renderSection(page, "Data / APIs", dataApis));
|
|
185
|
+
lines.push(...renderSection(page, "User flows", userFlows));
|
|
186
|
+
lines.push(...renderSection(page, "Constraints", constraints));
|
|
187
|
+
|
|
188
|
+
if (page.people?.length) {
|
|
189
|
+
lines.push("");
|
|
190
|
+
lines.push("## People");
|
|
191
|
+
for (const person of page.people) {
|
|
192
|
+
lines.push("");
|
|
193
|
+
lines.push(`### ${person.name}`);
|
|
194
|
+
if (person.role) {
|
|
195
|
+
lines.push(`**${person.role}**`);
|
|
196
|
+
}
|
|
197
|
+
if (person.affiliations?.length) {
|
|
198
|
+
lines.push(`*${person.affiliations.join(", ")}*`);
|
|
199
|
+
}
|
|
200
|
+
if (person.description) {
|
|
201
|
+
lines.push("");
|
|
202
|
+
lines.push(clean(page, person.description));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
lines.push("");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (page.initiatives?.length) {
|
|
209
|
+
lines.push("");
|
|
210
|
+
lines.push("## Initiatives");
|
|
211
|
+
for (const initiative of page.initiatives) {
|
|
212
|
+
const facts = initiative.facts?.length ? ` (${initiative.facts.join("; ")})` : "";
|
|
213
|
+
lines.push(`- ${initiative.name}: ${initiative.summary}${facts}`);
|
|
214
|
+
}
|
|
215
|
+
lines.push("");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (page.faqEntries?.length) {
|
|
219
|
+
lines.push("");
|
|
220
|
+
lines.push("## FAQ");
|
|
221
|
+
for (const entry of page.faqEntries) {
|
|
222
|
+
lines.push("");
|
|
223
|
+
lines.push(`### ${entry.question}`);
|
|
224
|
+
lines.push("");
|
|
225
|
+
lines.push(clean(page, entry.answer));
|
|
226
|
+
}
|
|
227
|
+
lines.push("");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return `${lines
|
|
231
|
+
.join("\n")
|
|
232
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
233
|
+
.trim()}\n`;
|
|
234
|
+
}
|