@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.
Files changed (403) hide show
  1. package/package.json +352 -0
  2. package/src/checks/index.ts +24 -0
  3. package/src/checks/lib/astro-site-url.ts +30 -0
  4. package/src/checks/lib/i18n.ts +30 -0
  5. package/src/checks/result-helpers.ts +102 -0
  6. package/src/checks/suppressions-config.ts +195 -0
  7. package/src/index.ts +14 -0
  8. package/src/integration/README.md +38 -0
  9. package/src/integration/adapters.ts +143 -0
  10. package/src/integration/crm-buffer.ts +491 -0
  11. package/src/integration/dispatch.ts +106 -0
  12. package/src/integration/funnel.ts +379 -0
  13. package/src/integration/index.ts +56 -0
  14. package/src/integration/lifecycle.ts +155 -0
  15. package/src/integration/orchestration.ts +439 -0
  16. package/src/integration/port-barrel.ts +124 -0
  17. package/src/integration/port.ts +143 -0
  18. package/src/integration/qstash.ts +134 -0
  19. package/src/integration/sharding.ts +133 -0
  20. package/src/integration/tests/deliver-event.test.ts +52 -0
  21. package/src/integration/tests/dispatch.test.ts +88 -0
  22. package/src/integration/tests/funnel-bridge.test.ts +46 -0
  23. package/src/integration/tests/hub-runtime.test.ts +128 -0
  24. package/src/integration/tests/lifecycle.test.ts +69 -0
  25. package/src/integration/tests/qstash.test.ts +83 -0
  26. package/src/integration/tests/sharding.test.ts +64 -0
  27. package/src/integration/vitest.config.ts +8 -0
  28. package/src/integration-adapter-supabase-crm/adapter.ts +437 -0
  29. package/src/integration-adapter-supabase-crm/client.ts +605 -0
  30. package/src/integration-adapter-supabase-crm/index.ts +28 -0
  31. package/src/integration-adapter-supabase-crm/pipedrive-sync-target.ts +482 -0
  32. package/src/integration-adapter-supabase-crm/supabase-rest.ts +89 -0
  33. package/src/integration-adapter-supabase-crm/tenant-registry.ts +334 -0
  34. package/src/integration-adapter-supabase-crm/tests/change-balance.pbt.test.ts +83 -0
  35. package/src/integration-adapter-supabase-crm/tests/funnel-persistence.test.ts +409 -0
  36. package/src/integration-adapter-supabase-crm/tests/lifecycle-sync.test.ts +288 -0
  37. package/src/integration-adapter-supabase-crm/tests/stage-map.test.ts +80 -0
  38. package/src/integration-adapter-supabase-crm/vitest.config.ts +8 -0
  39. package/src/integration-adapter-supabase-crm/worker.ts +223 -0
  40. package/src/observability/conventions.ts +93 -0
  41. package/src/observability/env-resolver.ts +57 -0
  42. package/src/observability/index.ts +57 -0
  43. package/src/observability/metric-registry.ts +184 -0
  44. package/src/observability/otlp-converter.ts +125 -0
  45. package/src/observability/otlp-json.ts +135 -0
  46. package/src/observability/otlp-transport.ts +68 -0
  47. package/src/observability/pusher.ts +142 -0
  48. package/src/observability/redact.ts +27 -0
  49. package/src/observability/tests/conventions.test.ts +100 -0
  50. package/src/observability/tests/metric-registry.test.ts +117 -0
  51. package/src/observability/tests/otlp-converter.pbt.test.ts +77 -0
  52. package/src/observability/tests/otlp-converter.test.ts +157 -0
  53. package/src/observability/tests/otlp-json.test.ts +120 -0
  54. package/src/observability/tests/pusher.test.ts +75 -0
  55. package/src/observability/tests/redact.pbt.test.ts +71 -0
  56. package/src/observability/tests/redact.test.ts +37 -0
  57. package/src/observability/typed-refs.ts +137 -0
  58. package/src/observability/vitest.config.ts +8 -0
  59. package/src/ontology/analytics/matomo-binding.yaml +172 -0
  60. package/src/ontology/analytics/matomo-fleet.registry.yaml +26 -0
  61. package/src/ontology/analytics/messkanon.yaml +151 -0
  62. package/src/ontology/archetype-registry.ts +61 -0
  63. package/src/ontology/archetypes/components/brand-label.yaml +19 -0
  64. package/src/ontology/archetypes/components/copyright.yaml +19 -0
  65. package/src/ontology/archetypes/components/currency-aware-price-display.yaml +19 -0
  66. package/src/ontology/archetypes/components/currency-selector.yaml +19 -0
  67. package/src/ontology/archetypes/components/footer-promo.yaml +19 -0
  68. package/src/ontology/archetypes/components/lang-switcher.yaml +19 -0
  69. package/src/ontology/archetypes/components/layout.yaml +19 -0
  70. package/src/ontology/archetypes/components/live-photo.yaml +20 -0
  71. package/src/ontology/archetypes/components/material-credit.yaml +20 -0
  72. package/src/ontology/archetypes/components/media.yaml +21 -0
  73. package/src/ontology/archetypes/components/nachweis-card.yaml +34 -0
  74. package/src/ontology/archetypes/components/nachweis-detail.yaml +43 -0
  75. package/src/ontology/archetypes/components/nachweis-list.yaml +37 -0
  76. package/src/ontology/archetypes/components/nachweis-verify.yaml +31 -0
  77. package/src/ontology/archetypes/components/not-found.yaml +22 -0
  78. package/src/ontology/archetypes/components/offer-capacity.yaml +21 -0
  79. package/src/ontology/archetypes/components/passport-header.yaml +19 -0
  80. package/src/ontology/archetypes/components/passport-provenance.yaml +20 -0
  81. package/src/ontology/archetypes/components/passport-score-grid.yaml +20 -0
  82. package/src/ontology/archetypes/components/passport-star-map.yaml +20 -0
  83. package/src/ontology/archetypes/components/person-profile.yaml +20 -0
  84. package/src/ontology/archetypes/components/pulsar.yaml +20 -0
  85. package/src/ontology/archetypes/components/responsive-image.yaml +19 -0
  86. package/src/ontology/archetypes/components/scroll-to-top.yaml +22 -0
  87. package/src/ontology/archetypes/components/social-meta.yaml +20 -0
  88. package/src/ontology/archetypes/components/structured-data.yaml +19 -0
  89. package/src/ontology/archetypes/index.json +1003 -0
  90. package/src/ontology/archetypes/index.yaml +821 -0
  91. package/src/ontology/archetypes/sections/approach.yaml +23 -0
  92. package/src/ontology/archetypes/sections/article-list.yaml +30 -0
  93. package/src/ontology/archetypes/sections/audience-cards.yaml +30 -0
  94. package/src/ontology/archetypes/sections/breadcrumbs.yaml +21 -0
  95. package/src/ontology/archetypes/sections/chat-widget.yaml +37 -0
  96. package/src/ontology/archetypes/sections/comparison-cards.yaml +31 -0
  97. package/src/ontology/archetypes/sections/controlled-responsibility-block.yaml +34 -0
  98. package/src/ontology/archetypes/sections/credits.yaml +29 -0
  99. package/src/ontology/archetypes/sections/donation-card.yaml +53 -0
  100. package/src/ontology/archetypes/sections/donation-use.yaml +23 -0
  101. package/src/ontology/archetypes/sections/dynamic-status-block.yaml +35 -0
  102. package/src/ontology/archetypes/sections/faq-list.yaml +26 -0
  103. package/src/ontology/archetypes/sections/final-cta.yaml +53 -0
  104. package/src/ontology/archetypes/sections/hero-decision-card.yaml +65 -0
  105. package/src/ontology/archetypes/sections/hero.yaml +50 -0
  106. package/src/ontology/archetypes/sections/impact.yaml +23 -0
  107. package/src/ontology/archetypes/sections/markdown.yaml +43 -0
  108. package/src/ontology/archetypes/sections/mountain-journey.yaml +43 -0
  109. package/src/ontology/archetypes/sections/navigation.yaml +20 -0
  110. package/src/ontology/archetypes/sections/notausgang-block.yaml +41 -0
  111. package/src/ontology/archetypes/sections/open-source-registry.yaml +26 -0
  112. package/src/ontology/archetypes/sections/ownership-block.yaml +57 -0
  113. package/src/ontology/archetypes/sections/people.yaml +48 -0
  114. package/src/ontology/archetypes/sections/price-card.yaml +50 -0
  115. package/src/ontology/archetypes/sections/problem.yaml +23 -0
  116. package/src/ontology/archetypes/sections/send-message.yaml +63 -0
  117. package/src/ontology/archetypes/sections/service-metadata-block.yaml +40 -0
  118. package/src/ontology/archetypes/sections/social-proof.yaml +27 -0
  119. package/src/ontology/archetypes/sections/transparency.yaml +32 -0
  120. package/src/ontology/archetypes/sections/trust-strip.yaml +42 -0
  121. package/src/ontology/archetypes/sections/video-section.yaml +27 -0
  122. package/src/ontology/archetypes/sections/women.yaml +29 -0
  123. package/src/ontology/biome-token-projection.ts +229 -0
  124. package/src/ontology/biomes/check-concrete-blueprint.yaml +118 -0
  125. package/src/ontology/biomes/handwerk-material-warm.yaml +135 -0
  126. package/src/ontology/biomes/nonprofit-trust.yaml +123 -0
  127. package/src/ontology/blueprints/offer.yaml +39 -0
  128. package/src/ontology/blueprints/prompts/local-market-signal.md +13 -0
  129. package/src/ontology/blueprints/prompts/website-local-narrative.md +24 -0
  130. package/src/ontology/blueprints/ratgeber.yaml +62 -0
  131. package/src/ontology/blueprints/website-local.yaml +309 -0
  132. package/src/ontology/blueprints/website-service.yaml +84 -0
  133. package/src/ontology/capabilities/lead.submit.yaml +44 -0
  134. package/src/ontology/cosmic/index.ts +48 -0
  135. package/src/ontology/cosmic/moon-catalog.ts +173 -0
  136. package/src/ontology/cosmic/planet-catalog.ts +82 -0
  137. package/src/ontology/cosmic/star-catalog.ts +337 -0
  138. package/src/ontology/enums.ts +159 -0
  139. package/src/ontology/external-surfaces/index.ts +158 -0
  140. package/src/ontology/external-surfaces/jsonld-types.yaml +135 -0
  141. package/src/ontology/external-surfaces/sitemap-shape.yaml +6 -0
  142. package/src/ontology/external-surfaces/url-schema.yaml +95 -0
  143. package/src/ontology/growth/events/contact-email-click.yaml +21 -0
  144. package/src/ontology/growth/events/contact-form-submit.yaml +25 -0
  145. package/src/ontology/growth/events/contact-phone-click.yaml +21 -0
  146. package/src/ontology/growth/events/contact-route-click.yaml +21 -0
  147. package/src/ontology/growth/events/contact-submit.yaml +28 -0
  148. package/src/ontology/growth/events/contact-whatsapp-click.yaml +21 -0
  149. package/src/ontology/growth/events/cta-click.yaml +30 -0
  150. package/src/ontology/growth/events/donation-confirmed.yaml +32 -0
  151. package/src/ontology/growth/events/donation-form-start.yaml +21 -0
  152. package/src/ontology/growth/events/donation-form-submit.yaml +29 -0
  153. package/src/ontology/growth/events/donation-intent.yaml +29 -0
  154. package/src/ontology/growth/events/form-error.yaml +27 -0
  155. package/src/ontology/growth/events/form-start.yaml +21 -0
  156. package/src/ontology/growth/events/form-submit.yaml +21 -0
  157. package/src/ontology/growth/events/outbound-click.yaml +25 -0
  158. package/src/ontology/growth/events/page-view.yaml +24 -0
  159. package/src/ontology/growth/events/passport-view.yaml +28 -0
  160. package/src/ontology/growth/events/star-map-navigate.yaml +30 -0
  161. package/src/ontology/index.ts +165 -0
  162. package/src/ontology/manifest.ts +344 -0
  163. package/src/ontology/operations/index.ts +1 -0
  164. package/src/ontology/schemas/biome.ts +303 -0
  165. package/src/ontology/schemas/capability.ts +104 -0
  166. package/src/ontology/schemas/constellation.ts +114 -0
  167. package/src/ontology/schemas/dns-records.ts +53 -0
  168. package/src/ontology/schemas/index.ts +95 -0
  169. package/src/ontology/schemas/manifest-resolver.ts +123 -0
  170. package/src/ontology/schemas/page-entry.ts +213 -0
  171. package/src/ontology/schemas/section-archetype.ts +109 -0
  172. package/src/ontology/schemas/site-family.ts +98 -0
  173. package/src/ontology/schemas/system/growth.ts +80 -0
  174. package/src/ontology/schemas/system/integrations.ts +143 -0
  175. package/src/ontology/schemas/system/manifest.ts +492 -0
  176. package/src/ontology/schemas/system/page-output.ts +99 -0
  177. package/src/ontology/schemas/system/release.ts +58 -0
  178. package/src/ontology/schemas/system/text.ts +58 -0
  179. package/src/ontology/schemas/system.ts +53 -0
  180. package/src/ontology/shared-section-props/body-fragments.ts +182 -0
  181. package/src/ontology/shared-section-props/catalog.ts +195 -0
  182. package/src/ontology/shared-section-props/common.ts +178 -0
  183. package/src/ontology/shared-section-props/index.ts +34 -0
  184. package/src/ontology/shared-section-props/visual-header.ts +163 -0
  185. package/src/ontology/site-families/charity-donation-trust/cultural-rules.yaml +6 -0
  186. package/src/ontology/site-families/charity-donation-trust/family.yaml +44 -0
  187. package/src/ontology/site-families/charity-donation-trust/linguistic-rules.yaml +6 -0
  188. package/src/ontology/site-families/check-quality-operator/cultural-rules.yaml +6 -0
  189. package/src/ontology/site-families/check-quality-operator/family.yaml +40 -0
  190. package/src/ontology/site-families/check-quality-operator/linguistic-rules.yaml +6 -0
  191. package/src/ontology/site-families/handwerk-trust-engineering/cultural-rules.yaml +6 -0
  192. package/src/ontology/site-families/handwerk-trust-engineering/family.yaml +53 -0
  193. package/src/ontology/site-families/handwerk-trust-engineering/linguistic-rules.yaml +6 -0
  194. package/src/ontology/tests/catalogs.pbt.test.ts +89 -0
  195. package/src/ontology/tests/enums.test.ts +203 -0
  196. package/src/ontology/tests/sternsystem-owner.test.ts +63 -0
  197. package/src/ontology/vitest.config.ts +8 -0
  198. package/src/passport/dht-sign.test.ts +99 -0
  199. package/src/passport/dht-sign.ts +73 -0
  200. package/src/passport/identity-sign.test.ts +134 -0
  201. package/src/passport/identity-sign.ts +85 -0
  202. package/src/passport/index.ts +53 -0
  203. package/src/passport/key-rotate.ts +133 -0
  204. package/src/passport/schema.ts +198 -0
  205. package/src/passport/sign.ts +225 -0
  206. package/src/passport/tests/schema.pbt.test.ts +93 -0
  207. package/src/passport/tests/schema.test.ts +222 -0
  208. package/src/passport/vitest.config.ts +8 -0
  209. package/src/share/agent/api-catalog.ts +77 -0
  210. package/src/share/agent/capability.ts +68 -0
  211. package/src/share/agent/dns-aid.test.ts +74 -0
  212. package/src/share/agent/dns-aid.ts +53 -0
  213. package/src/share/agent/fleet-catalog.ts +149 -0
  214. package/src/share/agent/index.ts +29 -0
  215. package/src/share/agent/knowledge.ts +96 -0
  216. package/src/share/agent/manifest.ts +126 -0
  217. package/src/share/agent/mcp-card.ts +63 -0
  218. package/src/share/agent/omit-empty.ts +77 -0
  219. package/src/share/agent/openapi.ts +157 -0
  220. package/src/share/agent/proof.ts +61 -0
  221. package/src/share/attribution-display.ts +86 -0
  222. package/src/share/check-hints.ts +38 -0
  223. package/src/share/content/entity-id.ts +65 -0
  224. package/src/share/content/index.ts +16 -0
  225. package/src/share/content/merge.ts +86 -0
  226. package/src/share/content/resolve-field-path.ts +39 -0
  227. package/src/share/content/substitute-deep.ts +47 -0
  228. package/src/share/content-discipline/index.ts +14 -0
  229. package/src/share/content-discipline/parsers.ts +66 -0
  230. package/src/share/content-discipline/types.ts +118 -0
  231. package/src/share/content-reference.ts +333 -0
  232. package/src/share/counter-utils.ts +75 -0
  233. package/src/share/css-value-normalize.ts +48 -0
  234. package/src/share/dev-props-validator.ts +131 -0
  235. package/src/share/entitlement.ts +172 -0
  236. package/src/share/env.d.ts +73 -0
  237. package/src/share/feature-policy.ts +367 -0
  238. package/src/share/formula-eval.ts +364 -0
  239. package/src/share/fs/index.ts +96 -0
  240. package/src/share/i18n/localization.ts +57 -0
  241. package/src/share/image-provider.ts +314 -0
  242. package/src/share/index.ts +86 -0
  243. package/src/share/knowledge/claim.ts +129 -0
  244. package/src/share/knowledge/derivation.ts +55 -0
  245. package/src/share/knowledge/freshness.ts +127 -0
  246. package/src/share/knowledge/index.ts +23 -0
  247. package/src/share/knowledge/ledger.ts +222 -0
  248. package/src/share/knowledge/plan.ts +122 -0
  249. package/src/share/knowledge/source.ts +170 -0
  250. package/src/share/legal/translation-policy.ts +133 -0
  251. package/src/share/material-credits.ts +181 -0
  252. package/src/share/middleware/language-redirect.ts +49 -0
  253. package/src/share/offer-capacity.ts +149 -0
  254. package/src/share/onboarding-yaml/index.ts +162 -0
  255. package/src/share/page.ts +351 -0
  256. package/src/share/redirects.test.ts +62 -0
  257. package/src/share/redirects.ts +54 -0
  258. package/src/share/rfc0042-utils.ts +95 -0
  259. package/src/share/runtime-context.ts +70 -0
  260. package/src/share/schemas/claim-records.ts +69 -0
  261. package/src/share/schemas/claims.ts +150 -0
  262. package/src/share/schemas/effects.ts +212 -0
  263. package/src/share/schemas/features.ts +147 -0
  264. package/src/share/schemas/horizontal-align.ts +22 -0
  265. package/src/share/schemas/icon-color.ts +27 -0
  266. package/src/share/schemas/index.ts +43 -0
  267. package/src/share/schemas/live-photo.ts +53 -0
  268. package/src/share/schemas/material-credit.ts +292 -0
  269. package/src/share/schemas/media.ts +220 -0
  270. package/src/share/schemas/navigation.ts +74 -0
  271. package/src/share/schemas/page-base.ts +33 -0
  272. package/src/share/schemas/participant.ts +247 -0
  273. package/src/share/schemas/person.ts +24 -0
  274. package/src/share/schemas/print.ts +93 -0
  275. package/src/share/schemas/section-background.ts +106 -0
  276. package/src/share/schemas/section-body.ts +138 -0
  277. package/src/share/schemas/section-cards.ts +37 -0
  278. package/src/share/schemas/section-cta.ts +74 -0
  279. package/src/share/schemas/section-header.ts +77 -0
  280. package/src/share/schemas/section-image.ts +77 -0
  281. package/src/share/schemas/section-motion.ts +52 -0
  282. package/src/share/schemas/section-shell.ts +63 -0
  283. package/src/share/schemas/section-stats.ts +37 -0
  284. package/src/share/schemas/site-background.ts +78 -0
  285. package/src/share/schemas/standard-list-item.ts +38 -0
  286. package/src/share/scripts/external-links.ts +45 -0
  287. package/src/share/scripts/gsap-counter.ts +168 -0
  288. package/src/share/scripts/gsap-mountain-journey.ts +236 -0
  289. package/src/share/scripts/gsap-parallax.ts +50 -0
  290. package/src/share/scripts/gsap-reveal.ts +65 -0
  291. package/src/share/scripts/gsap-stagger.ts +54 -0
  292. package/src/share/scripts/index.ts +25 -0
  293. package/src/share/scripts/inline-number-animation.ts +120 -0
  294. package/src/share/scripts/lenis.ts +109 -0
  295. package/src/share/scripts/live-photos.ts +250 -0
  296. package/src/share/scripts/lordicon.ts +94 -0
  297. package/src/share/scripts/orchestrator.ts +138 -0
  298. package/src/share/scripts/scheduler.ts +53 -0
  299. package/src/share/scripts/video-player.ts +116 -0
  300. package/src/share/semantic/ai.ts +140 -0
  301. package/src/share/semantic/block-extraction.ts +52 -0
  302. package/src/share/semantic/block-extractors/index.ts +502 -0
  303. package/src/share/semantic/breadcrumbs.ts +120 -0
  304. package/src/share/semantic/build-page.ts +309 -0
  305. package/src/share/semantic/business-projection.ts +278 -0
  306. package/src/share/semantic/derived-prices-loader.ts +36 -0
  307. package/src/share/semantic/extract.ts +118 -0
  308. package/src/share/semantic/feed.ts +124 -0
  309. package/src/share/semantic/ids.ts +76 -0
  310. package/src/share/semantic/image-sitemap.ts +153 -0
  311. package/src/share/semantic/index.ts +36 -0
  312. package/src/share/semantic/jsonld/article.ts +47 -0
  313. package/src/share/semantic/jsonld/breadcrumb.ts +47 -0
  314. package/src/share/semantic/jsonld/collection-list.ts +30 -0
  315. package/src/share/semantic/jsonld/context.ts +46 -0
  316. package/src/share/semantic/jsonld/faq.ts +49 -0
  317. package/src/share/semantic/jsonld/initiative.ts +53 -0
  318. package/src/share/semantic/jsonld/organization.ts +101 -0
  319. package/src/share/semantic/jsonld/person.ts +71 -0
  320. package/src/share/semantic/jsonld/service.ts +80 -0
  321. package/src/share/semantic/jsonld/shared.ts +31 -0
  322. package/src/share/semantic/jsonld/types.ts +18 -0
  323. package/src/share/semantic/jsonld/webpage.ts +98 -0
  324. package/src/share/semantic/jsonld/website.ts +27 -0
  325. package/src/share/semantic/jsonld.ts +75 -0
  326. package/src/share/semantic/llms-policy.ts +74 -0
  327. package/src/share/semantic/llms.ts +394 -0
  328. package/src/share/semantic/markdown-hygiene.ts +68 -0
  329. package/src/share/semantic/markdown-twin-provenance.ts +242 -0
  330. package/src/share/semantic/models.ts +402 -0
  331. package/src/share/semantic/organization-profile.ts +160 -0
  332. package/src/share/semantic/output-projection.ts +131 -0
  333. package/src/share/semantic/page-builders/markdown-page.ts +84 -0
  334. package/src/share/semantic/page-markdown.ts +234 -0
  335. package/src/share/semantic/page-utils.ts +125 -0
  336. package/src/share/semantic/price-marker-resolver.ts +92 -0
  337. package/src/share/semantic/robots.ts +127 -0
  338. package/src/share/semantic/update-stamp.ts +136 -0
  339. package/src/share/shared-context.ts +177 -0
  340. package/src/share/string-utils.ts +19 -0
  341. package/src/share/tests/api-catalog.test.ts +97 -0
  342. package/src/share/tests/breadcrumbs.test.ts +126 -0
  343. package/src/share/tests/build-page-price-markers.test.ts +160 -0
  344. package/src/share/tests/capability.test.ts +107 -0
  345. package/src/share/tests/claim.test.ts +90 -0
  346. package/src/share/tests/content-ref-index.test.ts +328 -0
  347. package/src/share/tests/create-dev-props-validator.test.ts +38 -0
  348. package/src/share/tests/derivation.test.ts +39 -0
  349. package/src/share/tests/dev-props-validator.test.ts +88 -0
  350. package/src/share/tests/fleet-catalog.test.ts +134 -0
  351. package/src/share/tests/formula-eval.test.ts +619 -0
  352. package/src/share/tests/freshness.test.ts +60 -0
  353. package/src/share/tests/hero-section-numbering-parity.test.ts +51 -0
  354. package/src/share/tests/image-sitemap.test.ts +101 -0
  355. package/src/share/tests/jsonld-service.test.ts +282 -0
  356. package/src/share/tests/knowledge.test.ts +86 -0
  357. package/src/share/tests/ledger.test.ts +125 -0
  358. package/src/share/tests/llms-0789.test.ts +63 -0
  359. package/src/share/tests/manifest.test.ts +100 -0
  360. package/src/share/tests/markdown-twin-provenance.test.ts +108 -0
  361. package/src/share/tests/mcp-card.test.ts +57 -0
  362. package/src/share/tests/onboarding-yaml.test.ts +111 -0
  363. package/src/share/tests/ontology-role-by-cosmic-name.test.ts +28 -0
  364. package/src/share/tests/openapi.test.ts +116 -0
  365. package/src/share/tests/organization-jsonld.test.ts +74 -0
  366. package/src/share/tests/output-projection.test.ts +61 -0
  367. package/src/share/tests/plan.test.ts +97 -0
  368. package/src/share/tests/price-marker-resolver.test.ts +141 -0
  369. package/src/share/tests/proof.test.ts +72 -0
  370. package/src/share/tests/resolve-field-path.test.ts +52 -0
  371. package/src/share/tests/source.test.ts +81 -0
  372. package/src/share/tests/substitute-deep.test.ts +65 -0
  373. package/src/share/tests/text-normalize.pbt.test.ts +143 -0
  374. package/src/share/tests/text-normalize.test.ts +285 -0
  375. package/src/share/tests/twin-path.test.ts +43 -0
  376. package/src/share/text-normalize.ts +548 -0
  377. package/src/share/text-position.ts +19 -0
  378. package/src/share/types/hls-js-light.d.ts +16 -0
  379. package/src/share/visibility.ts +167 -0
  380. package/src/share/vitest.config.ts +8 -0
  381. package/src/share/wrap-inline-numbers.ts +139 -0
  382. package/src/surface/blueprint-schema.ts +314 -0
  383. package/src/surface/blueprint-types.ts +256 -0
  384. package/src/surface/blueprint.ts +300 -0
  385. package/src/surface/breaker.ts +57 -0
  386. package/src/surface/decision-composer.ts +156 -0
  387. package/src/surface/eligibility.ts +372 -0
  388. package/src/surface/evidence-records.ts +97 -0
  389. package/src/surface/fleet.ts +60 -0
  390. package/src/surface/geo.ts +170 -0
  391. package/src/surface/governance.ts +146 -0
  392. package/src/surface/index.ts +216 -0
  393. package/src/surface/io/index.ts +18 -0
  394. package/src/surface/io/surface-module-context-io.ts +60 -0
  395. package/src/surface/io/visibility-outcomes-io.ts +46 -0
  396. package/src/surface/module-context.ts +120 -0
  397. package/src/surface/substance.ts +161 -0
  398. package/src/surface/tests/eligibility.pbt.test.ts +137 -0
  399. package/src/surface/tests/eligibility.test.ts +302 -0
  400. package/src/surface/types.ts +289 -0
  401. package/src/surface/visibility.ts +57 -0
  402. package/src/surface/vitest.config.ts +8 -0
  403. package/tsconfig.json +17 -0
@@ -0,0 +1,118 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Maintains packages/share/src/semantic/extract.ts as an authored share authored module so agents can evolve it without rediscovering local boundaries.</purpose>
4
+ <non-goals>
5
+ <item>Do not introduce app-specific runtime composition or deployment behavior into this reusable package source file.</item>
6
+ </non-goals>
7
+ </MODULE_CONTRACT>
8
+ <CHANGE_SUMMARY>
9
+ <item>RFC-0133: backfilled MODULE_MAP and CHANGE_SUMMARY markers for compass.validate compliance.</item>
10
+ </CHANGE_SUMMARY>
11
+ */
12
+
13
+ import type { SemanticPerson, SemanticPostalAddress } from "./models.ts";
14
+
15
+ export function normalizeWhitespace(value: string): string {
16
+ return value.replace(/\s+/g, " ").trim();
17
+ }
18
+
19
+ export function slugify(value: string): string {
20
+ return (
21
+ value
22
+ .toLowerCase()
23
+ .normalize("NFKD")
24
+ .replace(/[\u0300-\u036f]/g, "")
25
+ .replace(/[^a-z0-9]+/g, "-")
26
+ .replace(/^-+|-+$/g, "") || "entity"
27
+ );
28
+ }
29
+
30
+ export type MarkdownSection = {
31
+ heading: string;
32
+ body: string;
33
+ };
34
+
35
+ export function splitMarkdownSections(markdown: string, level: 2 | 3 = 2): MarkdownSection[] {
36
+ const normalizedMarkdown = markdown.trim();
37
+ if (!normalizedMarkdown) {
38
+ return [];
39
+ }
40
+
41
+ const marker = "#".repeat(level);
42
+ const splitPattern = new RegExp(`\\n(?=${marker}\\s+)`);
43
+ const headingPattern = new RegExp(`^${marker}\\s+`);
44
+
45
+ return normalizedMarkdown
46
+ .split(splitPattern)
47
+ .map((section) => section.trim())
48
+ .filter((section) => headingPattern.test(section))
49
+ .map((section) => {
50
+ const [headingLine = "", ...restLines] = section.split("\n");
51
+ return {
52
+ heading: headingLine.replace(headingPattern, "").trim(),
53
+ body: restLines.join("\n").trim(),
54
+ };
55
+ });
56
+ }
57
+
58
+ export function extractParagraphs(markdown: string): string[] {
59
+ return markdown
60
+ .split(/\n\s*\n/)
61
+ .map((part) => normalizeWhitespace(part))
62
+ .filter(Boolean);
63
+ }
64
+
65
+ export function extractListFacts(markdown: string): string[] {
66
+ return markdown
67
+ .split("\n")
68
+ .map((line) => line.trim())
69
+ .filter((line) => line.startsWith("- ") || /^\d+\.\s+/.test(line))
70
+ .map((line) =>
71
+ line
72
+ .replace(/^-\s+/, "")
73
+ .replace(/^\d+\.\s+/, "")
74
+ .trim(),
75
+ )
76
+ .filter(Boolean);
77
+ }
78
+
79
+ export function extractEmail(markdown: string): string | undefined {
80
+ const match = markdown.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i);
81
+ return match?.[0];
82
+ }
83
+
84
+ export function extractPostalAddress(
85
+ markdown: string,
86
+ country: string = "DE",
87
+ ): SemanticPostalAddress | undefined {
88
+ const match = markdown.match(/([^\n,]+),\s*(\d{5})\s+([^\n]+)/);
89
+
90
+ if (!match) {
91
+ return undefined;
92
+ }
93
+
94
+ return {
95
+ streetAddress: match[1].trim(),
96
+ postalCode: match[2].trim(),
97
+ addressLocality: match[3].trim(),
98
+ addressCountry: country,
99
+ };
100
+ }
101
+
102
+ export function mergePeople(...groups: Array<SemanticPerson[] | undefined>): SemanticPerson[] {
103
+ const merged = new Map<string, SemanticPerson>();
104
+
105
+ for (const group of groups) {
106
+ for (const person of group ?? []) {
107
+ const existing = merged.get(person.name);
108
+ merged.set(person.name, {
109
+ name: person.name,
110
+ role: existing?.role ?? person.role,
111
+ description: existing?.description ?? person.description,
112
+ isDeceased: existing?.isDeceased || person.isDeceased,
113
+ });
114
+ }
115
+ }
116
+
117
+ return Array.from(merged.values());
118
+ }
@@ -0,0 +1,124 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>RFC-0165/RFC-0317: pure RSS 2.0 + JSON Feed v1.1 formatters. Framework-agnostic —
4
+ the site-OS feed.generate command supplies the channel + dated items and writes the result.</purpose>
5
+ <non-goals>
6
+ <item>Do not read files or fetch content — the caller supplies resolved items.</item>
7
+ </non-goals>
8
+ </MODULE_CONTRACT>
9
+ <CHANGE_SUMMARY>
10
+ <item>RFC-0165: initial implementation.</item>
11
+ <item>RFC-0317: add JSON Feed v1.1 output from the same item set.</item>
12
+ </CHANGE_SUMMARY>
13
+ */
14
+
15
+ export interface FeedItem {
16
+ title: string;
17
+ /** Absolute URL. */
18
+ url: string;
19
+ summary: string;
20
+ /** ISO date. */
21
+ publishedAt: string;
22
+ /** ISO date; defaults to publishedAt. */
23
+ updatedAt?: string;
24
+ }
25
+
26
+ export interface FeedChannel {
27
+ title: string;
28
+ /** Absolute site URL. */
29
+ url: string;
30
+ description: string;
31
+ language: string;
32
+ /** Absolute URL of the feed itself (Atom self-link). */
33
+ selfUrl: string;
34
+ }
35
+
36
+ function escapeXml(value: string): string {
37
+ return value
38
+ .replace(/&/g, "&amp;")
39
+ .replace(/</g, "&lt;")
40
+ .replace(/>/g, "&gt;")
41
+ .replace(/"/g, "&quot;")
42
+ .replace(/'/g, "&apos;");
43
+ }
44
+
45
+ function toRfc822(iso: string): string {
46
+ const date = new Date(iso);
47
+ return Number.isNaN(date.getTime()) ? iso : date.toUTCString();
48
+ }
49
+
50
+ export function buildRssFeed(channel: FeedChannel, items: FeedItem[]): string {
51
+ const sorted = [...items].sort(
52
+ (a, b) => new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime(),
53
+ );
54
+ // Deterministic: derive lastBuildDate from item dates (never `now`), and omit it for an
55
+ // empty feed so a no-article app produces a stable, churn-free file.
56
+ const lastBuild = sorted[0]?.updatedAt ?? sorted[0]?.publishedAt;
57
+
58
+ const itemXml = sorted
59
+ .map((item) =>
60
+ [
61
+ " <item>",
62
+ ` <title>${escapeXml(item.title)}</title>`,
63
+ ` <link>${escapeXml(item.url)}</link>`,
64
+ ` <guid isPermaLink="true">${escapeXml(item.url)}</guid>`,
65
+ ` <description>${escapeXml(item.summary)}</description>`,
66
+ ` <pubDate>${escapeXml(toRfc822(item.publishedAt))}</pubDate>`,
67
+ " </item>",
68
+ ].join("\n"),
69
+ )
70
+ .join("\n");
71
+
72
+ return [
73
+ '<?xml version="1.0" encoding="UTF-8"?>',
74
+ '<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">',
75
+ " <channel>",
76
+ ` <title>${escapeXml(channel.title)}</title>`,
77
+ ` <link>${escapeXml(channel.url)}</link>`,
78
+ ` <description>${escapeXml(channel.description)}</description>`,
79
+ ` <language>${escapeXml(channel.language)}</language>`,
80
+ ...(lastBuild ? [` <lastBuildDate>${escapeXml(toRfc822(lastBuild))}</lastBuildDate>`] : []),
81
+ ` <atom:link href="${escapeXml(channel.selfUrl)}" rel="self" type="application/rss+xml" />`,
82
+ ...(itemXml ? [itemXml] : []),
83
+ " </channel>",
84
+ "</rss>",
85
+ "",
86
+ ].join("\n");
87
+ }
88
+
89
+ export interface JsonFeedInput {
90
+ title: string;
91
+ home_page_url: string;
92
+ feed_url: string;
93
+ description: string;
94
+ language: string;
95
+ items: FeedItem[];
96
+ }
97
+
98
+ /** RFC-0317: JSON Feed v1.1 from the same item set as the RSS feed. */
99
+ export function buildJsonFeed(input: JsonFeedInput): string {
100
+ const sorted = [...input.items].sort(
101
+ (a, b) => new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime(),
102
+ );
103
+
104
+ const feed: Record<string, unknown> = {
105
+ version: "https://jsonfeed.org/version/1.1",
106
+ title: input.title,
107
+ description: input.description || undefined,
108
+ home_page_url: input.home_page_url,
109
+ feed_url: input.feed_url,
110
+ language: input.language,
111
+ };
112
+ if (sorted.length > 0) {
113
+ feed.items = sorted.map((item) => ({
114
+ id: item.url,
115
+ url: item.url,
116
+ title: item.title,
117
+ content_text: item.summary || undefined,
118
+ date_published: item.publishedAt,
119
+ ...(item.updatedAt ? { date_modified: item.updatedAt } : {}),
120
+ }));
121
+ }
122
+
123
+ return `${JSON.stringify(feed, null, 2)}\n`;
124
+ }
@@ -0,0 +1,76 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>URL transformations and semantic ID generation for structured data. Framework-agnostic — used across all Warpgogol apps.</purpose>
4
+ <non-goals>
5
+ <item>Do not perform raw URL parsing beyond basic structure validation.</item>
6
+ <item>Do not manage application configuration or external service interactions.</item>
7
+ </non-goals>
8
+ </MODULE_CONTRACT>
9
+ <CHANGE_SUMMARY>
10
+ <item>Moved from app semantic/ids to packages/share/src/semantic/ids.ts — framework-agnostic, reusable across all apps.</item>
11
+ </CHANGE_SUMMARY>
12
+ */
13
+
14
+ export function getBaseUrl(url: URL | string): string {
15
+ const resolvedUrl = new URL(typeof url === "string" ? url : url.toString());
16
+ return resolvedUrl.origin;
17
+ }
18
+
19
+ export function toCanonicalUrl(url: URL | string): string {
20
+ const resolvedUrl = new URL(typeof url === "string" ? url : url.toString());
21
+ resolvedUrl.search = "";
22
+ resolvedUrl.hash = "";
23
+ return resolvedUrl.toString();
24
+ }
25
+
26
+ export function toAbsoluteUrl(baseUrl: string, path: string): string {
27
+ return new URL(path, `${baseUrl}/`).toString();
28
+ }
29
+
30
+ export function toPathname(url: string): string {
31
+ return new URL(url).pathname;
32
+ }
33
+
34
+ export function markdownTwinRelPath(
35
+ pathname: string,
36
+ opts: { supportedLangs: readonly string[] },
37
+ ): string {
38
+ const p = pathname.replace(/^\/+|\/+$/g, "");
39
+ if (p === "") return "index.md";
40
+ const isLangRoot = !p.includes("/") && opts.supportedLangs.includes(p);
41
+ if (isLangRoot) return `${p}/index.md`;
42
+ return `${p}.md`;
43
+ }
44
+
45
+ export function markdownTwinUrlPath(
46
+ pathname: string,
47
+ opts: { supportedLangs: readonly string[] },
48
+ ): string {
49
+ return `/${markdownTwinRelPath(pathname, opts)}`;
50
+ }
51
+
52
+ function toIdSegment(value: string): string {
53
+ return (
54
+ value
55
+ .toLowerCase()
56
+ .normalize("NFKD")
57
+ .replace(/[\u0300-\u036f]/g, "")
58
+ .replace(/[^a-z0-9]+/g, "-")
59
+ .replace(/^-+|-+$/g, "") || "entity"
60
+ );
61
+ }
62
+
63
+ export function createSemanticIds(baseUrl: string) {
64
+ const normalizedBaseUrl = baseUrl.replace(/\/$/, "");
65
+
66
+ return {
67
+ organization: `${normalizedBaseUrl}/#/schema/organization`,
68
+ website: `${normalizedBaseUrl}/#/schema/website`,
69
+ webpage: (pageUrl: string) => `${toCanonicalUrl(pageUrl)}#/schema/webpage`,
70
+ breadcrumb: (pageUrl: string) => `${toCanonicalUrl(pageUrl)}#/schema/breadcrumb`,
71
+ person: (name: string) => `${normalizedBaseUrl}/#/schema/person/${toIdSegment(name)}`,
72
+ initiative: (name: string) => `${normalizedBaseUrl}/#/schema/initiative/${toIdSegment(name)}`,
73
+ service: (name: string) => `${normalizedBaseUrl}/#/schema/service/${toIdSegment(name)}`,
74
+ faq: (pageUrl: string) => `${toCanonicalUrl(pageUrl)}#/schema/faq`,
75
+ };
76
+ }
@@ -0,0 +1,153 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>RFC-0172: pure harvester + formatter for the post-build image sitemap. Reads
4
+ rendered HTML (supplied by the caller) and extracts the page's single lead/content image,
5
+ then formats a Google image sitemap. Framework-agnostic — the site-OS dist.sitemap.images.generate
6
+ command walks dist/client and writes the result; this module touches no filesystem.</purpose>
7
+ <non-goals>
8
+ <item>Do not read files or fetch HTML — the caller supplies the HTML string.</item>
9
+ <item>Do not construct /_astro or /cdn-cgi/image URLs — only read what the render emitted (RFC-0152).</item>
10
+ <item>Do not apply policy/severity — the command decides pass/fail from the harvest result.</item>
11
+ </non-goals>
12
+ </MODULE_CONTRACT>
13
+ <CHANGE_SUMMARY>
14
+ <item>RFC-0172: initial implementation.</item>
15
+ </CHANGE_SUMMARY>
16
+ */
17
+
18
+ /** One resolved image entry harvested from rendered HTML. */
19
+ export interface SitemapImageEntry {
20
+ /** Absolute page URL (canonical, from the document). */
21
+ loc: string;
22
+ /** Absolute image URL; provider-emitted (/_astro/<hash> or /cdn-cgi/image/...). */
23
+ imageUrl: string;
24
+ /** From the `<img alt>` / `og:image:alt`, when present. */
25
+ title?: string;
26
+ }
27
+
28
+ /** Result of harvesting one rendered HTML document. */
29
+ export interface ContentImageHarvest {
30
+ /** Canonical absolute page URL, or null when no canonical link is present. */
31
+ loc: string | null;
32
+ /** All distinct content-image signals found, absolutized. >1 is a uniqueness violation. */
33
+ imageUrls: string[];
34
+ /** Best-effort image title (alt text), when discoverable. */
35
+ title?: string;
36
+ }
37
+
38
+ /**
39
+ * True when the HTML is a meta-refresh redirect stub (no real content) — e.g. the
40
+ * RFC-0160 prefixed-default-language `/de/…` → `/…` stubs.
41
+ *
42
+ * Note: we deliberately do NOT treat `window.location` as a stub signal. Full
43
+ * content pages (the root home, RFC-0159) carry a *soft* client-side language
44
+ * redirect for non-default browser locales while still serving complete HTML;
45
+ * those must be harvested, not skipped.
46
+ */
47
+ export function isHtmlRedirectPage(html: string): boolean {
48
+ return /<meta[^>]+http-equiv=["']refresh["']/i.test(html);
49
+ }
50
+
51
+ /** RFC-0150 preview screenshots and the og-image fallback are NOT content images. */
52
+ export function isSyntheticPreviewUrl(url: string): boolean {
53
+ return /\/preview\/[^"']*\.png(\?|$)/i.test(url) || /\/og-image\.png(\?|$)/i.test(url);
54
+ }
55
+
56
+ function extractCanonical(html: string, siteUrl: string): string | null {
57
+ const m = html.match(/<link[^>]+rel=["']canonical["'][^>]*href=["']([^"']+)["']/i);
58
+ if (!m?.[1]) return null;
59
+ return absolutize(m[1], siteUrl);
60
+ }
61
+
62
+ /** Absolutize a possibly-relative URL against the site origin. Returns null if unresolvable. */
63
+ function absolutize(url: string, siteUrl: string): string | null {
64
+ try {
65
+ return new URL(url, ensureTrailingSlash(siteUrl)).toString();
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
70
+
71
+ function ensureTrailingSlash(origin: string): string {
72
+ return origin.endsWith("/") ? origin : `${origin}/`;
73
+ }
74
+
75
+ /** Pull the value of a single attribute from an HTML tag string. */
76
+ function attr(tag: string, name: string): string | undefined {
77
+ const m = tag.match(new RegExp(`${name}=["']([^"']*)["']`, "i"));
78
+ return m?.[1];
79
+ }
80
+
81
+ /**
82
+ * Harvest the page's content image(s) from one rendered HTML document.
83
+ *
84
+ * Two signal sources, unified (RFC-0172):
85
+ * 1. `<img ... data-content-image ...>` — render-resolved hero `leadImage` (hashed URL).
86
+ * 2. `<meta name="x-content-image" content="...">` — authored `output.image` (absolute).
87
+ *
88
+ * Returns every distinct, absolutized URL found so the caller can enforce the
89
+ * one-per-page contract. Synthetic preview/og-image URLs are excluded here.
90
+ */
91
+ export function harvestContentImage(html: string, siteUrl: string): ContentImageHarvest {
92
+ const loc = extractCanonical(html, siteUrl);
93
+ const urls = new Set<string>();
94
+ let title: string | undefined;
95
+
96
+ // 1. <img data-content-image>
97
+ const imgRe = /<img\b[^>]*\bdata-content-image\b[^>]*>/gi;
98
+ let im: RegExpExecArray | null;
99
+ while ((im = imgRe.exec(html)) !== null) {
100
+ const tag = im[0];
101
+ const src = attr(tag, "src");
102
+ if (!src) continue;
103
+ const abs = absolutize(src, siteUrl);
104
+ if (!abs || isSyntheticPreviewUrl(abs)) continue;
105
+ urls.add(abs);
106
+ if (!title) title = attr(tag, "alt") || undefined;
107
+ }
108
+
109
+ // 2. <meta name="x-content-image" content="...">
110
+ const metaRe = /<meta[^>]+name=["']x-content-image["'][^>]*content=["']([^"']+)["'][^>]*>/gi;
111
+ let mm: RegExpExecArray | null;
112
+ while ((mm = metaRe.exec(html)) !== null) {
113
+ const abs = absolutize(mm[1], siteUrl);
114
+ if (!abs || isSyntheticPreviewUrl(abs)) continue;
115
+ urls.add(abs);
116
+ }
117
+
118
+ // Fallback title from og:image:alt when an <img> alt was unavailable.
119
+ if (!title) {
120
+ const altMeta = html.match(
121
+ /<meta[^>]+property=["']og:image:alt["'][^>]*content=["']([^"']+)["']/i,
122
+ );
123
+ if (altMeta?.[1]) title = altMeta[1];
124
+ }
125
+
126
+ return { loc, imageUrls: [...urls], ...(title ? { title } : {}) };
127
+ }
128
+
129
+ function escapeXml(value: string): string {
130
+ return value
131
+ .replace(/&/g, "&amp;")
132
+ .replace(/"/g, "&quot;")
133
+ .replace(/</g, "&lt;")
134
+ .replace(/>/g, "&gt;");
135
+ }
136
+
137
+ /**
138
+ * Render the image sitemap XML. Each entry becomes one `<url>` with an
139
+ * `<image:image>` child. An empty entry list yields a valid empty urlset.
140
+ * The caller is responsible for prepending the GENERATED marker if desired.
141
+ */
142
+ export function generateImageSitemapXml(entries: SitemapImageEntry[]): string {
143
+ const urls = entries
144
+ .map((e) => {
145
+ const titleXml = e.title ? `\n <image:title>${escapeXml(e.title)}</image:title>` : "";
146
+ return ` <url>\n <loc>${escapeXml(e.loc)}</loc>\n <image:image>\n <image:loc>${escapeXml(
147
+ e.imageUrl,
148
+ )}</image:loc>${titleXml}\n </image:image>\n </url>`;
149
+ })
150
+ .join("\n");
151
+
152
+ return `<?xml version="1.0" encoding="UTF-8"?>\n<urlset\n xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"\n xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"\n>\n${urls}\n</urlset>`;
153
+ }
@@ -0,0 +1,36 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Barrel export for the entire semantic layer: models, extractors, IDs, JSON-LD, LLMs projections, page builders, and utilities.</purpose>
4
+ <non-goals>
5
+ <item>Do not contain implementation logic — this is a barrel file only.</item>
6
+ </non-goals>
7
+ </MODULE_CONTRACT>
8
+ <CHANGE_SUMMARY>
9
+ <item>Expanded barrel to cover the full semantic layer: ids, jsonld, llms, page-utils, page-builders.</item>
10
+ </CHANGE_SUMMARY>
11
+ */
12
+
13
+ export * from "./models.ts";
14
+ export * from "./breadcrumbs.ts";
15
+ export * from "./extract.ts";
16
+ export * from "./ids.ts";
17
+ export * from "./jsonld.ts";
18
+ export * from "./markdown-hygiene.ts";
19
+ export * from "./llms.ts";
20
+ export * from "./llms-policy.ts";
21
+ export * from "./output-projection.ts";
22
+ export * from "./business-projection.ts";
23
+ export * from "./organization-profile.ts";
24
+ export * from "./ai.ts";
25
+ export * from "./robots.ts";
26
+ export * from "./page-utils.ts";
27
+ export * from "./page-markdown.ts";
28
+ export * from "./feed.ts";
29
+ export * from "./image-sitemap.ts";
30
+ export * from "./page-builders/markdown-page.ts";
31
+ export * from "./build-page.ts";
32
+ export * from "./block-extraction.ts";
33
+ export * from "./block-extractors/index.ts";
34
+ export * from "./update-stamp.ts";
35
+ export * from "./markdown-twin-provenance.ts";
36
+ export * from "./price-marker-resolver.ts";
@@ -0,0 +1,47 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>RFC-0167: builds the Article/BlogPosting JSON-LD node from a page that carries article
4
+ metadata (datePublished/author/…). Gives Google freshness/Discover signals and LLM recency +
5
+ attribution that a bare WebPage lacks.</purpose>
6
+ <non-goals>
7
+ <item>Do not emit when the page has no datePublished.</item>
8
+ </non-goals>
9
+ </MODULE_CONTRACT>
10
+ <CHANGE_SUMMARY>
11
+ <item>RFC-0167: initial implementation.</item>
12
+ </CHANGE_SUMMARY>
13
+ */
14
+
15
+ import type { JsonLdContext } from "./context.ts";
16
+ import type { JsonLdNode } from "./types.ts";
17
+
18
+ export function buildArticleNode(context: JsonLdContext): JsonLdNode | null {
19
+ const { page, ids, webpageId } = context;
20
+ if (!page.datePublished) {
21
+ return null;
22
+ }
23
+ const isRatgeberDepth1 = page.surfaceId === "ratgeber" && page.depth === 1;
24
+ const author = page.authorRecord
25
+ ? {
26
+ "@type": "Person",
27
+ name: page.authorRecord.name,
28
+ ...(page.authorRecord.contactUrl ? { url: page.authorRecord.contactUrl } : {}),
29
+ }
30
+ : page.author
31
+ ? { "@type": "Person", name: page.author }
32
+ : undefined;
33
+ return {
34
+ "@type": ["Article", "BlogPosting"],
35
+ "@id": webpageId.replace("#/schema/webpage", "#/schema/article"),
36
+ headline: page.heading ?? page.title,
37
+ description: page.description,
38
+ datePublished: page.datePublished,
39
+ dateModified: page.dateModified ?? page.datePublished,
40
+ ...(author ? { author } : {}),
41
+ publisher: { "@id": ids.organization },
42
+ mainEntityOfPage: isRatgeberDepth1 ? page.url : { "@id": webpageId },
43
+ inLanguage: page.lang,
44
+ ...(page.primaryImage ? { image: page.primaryImage.url } : {}),
45
+ ...(page.keywords?.length ? { keywords: page.keywords.join(", ") } : {}),
46
+ };
47
+ }
@@ -0,0 +1,47 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Maintains packages/share/src/semantic/jsonld/breadcrumb.ts as an authored share authored module so agents can evolve it without rediscovering local boundaries.</purpose>
4
+ <non-goals>
5
+ <item>Do not parse raw content.</item>
6
+ </non-goals>
7
+ </MODULE_CONTRACT>
8
+ <CHANGE_SUMMARY>
9
+ <item>Added breadcrumb support for JSON-LD generation.</item>
10
+ <item>Moved from app semantic/jsonld/breadcrumb to packages/share.</item>
11
+ </CHANGE_SUMMARY>
12
+ */
13
+
14
+ import type { JsonLdContext } from "./context.ts";
15
+ import type { JsonLdNode } from "./types.ts";
16
+
17
+ export function buildBreadcrumbNode(context: JsonLdContext): JsonLdNode | null {
18
+ const { page, breadcrumbId } = context;
19
+
20
+ if (page.breadcrumbs.length <= 1) {
21
+ return null;
22
+ }
23
+
24
+ // RFC-0229: trail crumbs carry root-relative paths so the *rendered* links work in any environment.
25
+ // The BreadcrumbList `item` must be an absolute canonical URL, so resolve each crumb against the
26
+ // page's canonical origin (already-absolute crumb URLs pass through unchanged).
27
+ const toAbsolute = (url: string): string => {
28
+ try {
29
+ return new URL(url, page.url).toString();
30
+ } catch {
31
+ return url;
32
+ }
33
+ };
34
+
35
+ return {
36
+ "@type": "BreadcrumbList",
37
+ "@id": breadcrumbId,
38
+ itemListElement: page.breadcrumbs.map((item, index) => ({
39
+ "@type": "ListItem",
40
+ // RFC-0229: stable, position-scoped @id so each crumb is an addressable node in the page graph.
41
+ "@id": `${breadcrumbId}/${index + 1}`,
42
+ position: index + 1,
43
+ name: item.name,
44
+ item: toAbsolute(item.url),
45
+ })),
46
+ };
47
+ }
@@ -0,0 +1,30 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Builds the JSON-LD ItemList node for "collection"-typed pages (RFC-0490 pillar hub).</purpose>
4
+ <non-goals>
5
+ <item>Do not build per-item Product or Service nodes — the ItemList references page URLs only.</item>
6
+ </non-goals>
7
+ </MODULE_CONTRACT>
8
+ <CHANGE_SUMMARY>
9
+ <item>RFC-0490: initial — ItemList node for collection-typed pillar hub pages.</item>
10
+ </CHANGE_SUMMARY>
11
+ */
12
+
13
+ import type { JsonLdContext } from "./context.ts";
14
+ import type { JsonLdNode } from "./types.ts";
15
+
16
+ export function buildCollectionListNode(context: JsonLdContext): JsonLdNode | null {
17
+ const { page, collectionListId } = context;
18
+ if (!collectionListId || !page.collectionItems?.length) return null;
19
+
20
+ return {
21
+ "@type": "ItemList",
22
+ "@id": collectionListId,
23
+ itemListElement: page.collectionItems.map((item, index) => ({
24
+ "@type": "ListItem",
25
+ position: index + 1,
26
+ url: item.url,
27
+ name: item.name,
28
+ })),
29
+ };
30
+ }
@@ -0,0 +1,46 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Creates a typed context object for JSON-LD node builders from a SemanticPageModel.</purpose>
4
+ <non-goals>
5
+ <item>Do not parse or transform content.</item>
6
+ <item>Do not fetch external data.</item>
7
+ </non-goals>
8
+ </MODULE_CONTRACT>
9
+ <CHANGE_SUMMARY>
10
+ <item>Added servicesListId and initiativesListId derived IDs.</item>
11
+ <item>Moved from app semantic/jsonld/context to packages/share.</item>
12
+ </CHANGE_SUMMARY>
13
+ */
14
+
15
+ import { createSemanticIds } from "../ids.ts";
16
+ import type { SemanticPageModel } from "../models.ts";
17
+
18
+ export type JsonLdContext = {
19
+ page: SemanticPageModel;
20
+ ids: ReturnType<typeof createSemanticIds>;
21
+ webpageId: string;
22
+ breadcrumbId: string;
23
+ initiativesListId: string;
24
+ servicesListId?: string;
25
+ faqPageId?: string;
26
+ collectionListId?: string;
27
+ };
28
+
29
+ export function createJsonLdContext(page: SemanticPageModel): JsonLdContext {
30
+ const ids = createSemanticIds(new URL(page.url).origin);
31
+ const webpageId = ids.webpage(page.url);
32
+
33
+ return {
34
+ page,
35
+ ids,
36
+ webpageId,
37
+ breadcrumbId: ids.breadcrumb(page.url),
38
+ initiativesListId: `${webpageId}/initiatives`,
39
+ servicesListId: page.organization.services?.length ? `${ids.organization}/services` : undefined,
40
+ faqPageId: page.faqEntries && page.faqEntries.length > 0 ? ids.faq(page.url) : undefined,
41
+ collectionListId:
42
+ page.type === "collection" && page.collectionItems?.length
43
+ ? `${webpageId}/industries`
44
+ : undefined,
45
+ };
46
+ }