@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,125 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Shared utilities for semantic page builders: slug generation, markdown answer block extraction, and conversion helpers.</purpose>
4
+ <non-goals>
5
+ <item>Do not contain page-specific logic or component content loading.</item>
6
+ <item>Do not interact with astro:content — use astro/semantic-page.ts for that.</item>
7
+ </non-goals>
8
+ </MODULE_CONTRACT>
9
+ <CHANGE_SUMMARY>
10
+ <item>Extracted from app semantic pages _shared — framework-agnostic utilities moved to packages/share.</item>
11
+ <item>Unified single blocksToMarkdown signature (was duplicated with different signatures in app).</item>
12
+ <item>Added slugify export and markdown answer-block extraction utilities.</item>
13
+ <item>RFC-0372: toSemanticAnswerBlocks now returns SemanticBlock[] with blockType: "prose".</item>
14
+ </CHANGE_SUMMARY>
15
+ */
16
+
17
+ import type { SemanticBlock } from "./models.ts";
18
+ import { slugify } from "./extract.ts";
19
+
20
+ export { slugify };
21
+
22
+ /**
23
+ * Creates a URL-friendly slug from a string.
24
+ * Re-exports from extract.ts to keep page-utils self-contained for consumers.
25
+ */
26
+
27
+ /**
28
+ * Extracts structured answer blocks from markdown body text.
29
+ * Looks for h2 headings (## ...) and captures content until the next h2 or end of document.
30
+ */
31
+ export function extractAnswerBlocksFromMarkdown(
32
+ bodyText: string,
33
+ ): Array<{ heading: string; content: string }> {
34
+ const blocks: Array<{ heading: string; content: string }> = [];
35
+ const lines = bodyText.split("\n");
36
+ let currentHeading = "";
37
+ let currentContent: string[] = [];
38
+
39
+ for (const line of lines) {
40
+ const h2Match = line.match(/^##\s+(.+)$/);
41
+ if (h2Match) {
42
+ if (currentHeading && currentContent.length > 0) {
43
+ blocks.push({
44
+ heading: currentHeading,
45
+ content: currentContent.join("\n").trim(),
46
+ });
47
+ }
48
+ currentHeading = h2Match[1];
49
+ currentContent = [];
50
+ } else if (currentHeading) {
51
+ currentContent.push(line);
52
+ }
53
+ }
54
+
55
+ if (currentHeading && currentContent.length > 0) {
56
+ blocks.push({
57
+ heading: currentHeading,
58
+ content: currentContent.join("\n").trim(),
59
+ });
60
+ }
61
+
62
+ return blocks;
63
+ }
64
+
65
+ /**
66
+ * Converts structured answer blocks back to markdown format.
67
+ */
68
+ export function blocksToMarkdown(blocks: Array<{ heading: string; content: string }>): string {
69
+ return blocks
70
+ .map((block) => `## ${block.heading}\n\n${block.content}`)
71
+ .join("\n\n")
72
+ .trim();
73
+ }
74
+
75
+ /**
76
+ * Generates a page entry ID from a URL path.
77
+ * Removes language prefix and .html extension, then converts to kebab-case.
78
+ */
79
+ export function toPageEntryId(url: URL): string {
80
+ const pathname = url.pathname;
81
+ const withoutLang = pathname.replace(/^\/[a-z]{2}\//, "/");
82
+ const clean = withoutLang.replace(/^\//, "").replace(/\.html$/, "");
83
+ return clean.replace(/\//g, "-") || "index";
84
+ }
85
+
86
+ /**
87
+ * RFC-0372: Transforms markdown answer blocks to SemanticBlock[] with blockType: "prose".
88
+ * Adds id, summary, and facts fields according to SemanticBlock type.
89
+ */
90
+ export function toSemanticAnswerBlocks(
91
+ blocks: Array<{ heading: string; content: string }>,
92
+ ): SemanticBlock[] {
93
+ return blocks.map((block) => {
94
+ // A GFM table or a section with more than one paragraph carries structure that a flat
95
+ // one-line-per-fact split would destroy (table rows becoming stray bullets, an unrelated
96
+ // closing paragraph merging into the fact list). Preserve that content verbatim as the
97
+ // block summary instead of fragmenting it.
98
+ const hasTable = /^\s*\|/m.test(block.content);
99
+ const hasMultipleParagraphs = /\n[ \t]*\n/.test(block.content.trim());
100
+ if (hasTable || hasMultipleParagraphs) {
101
+ return {
102
+ id: slugify(block.heading),
103
+ blockType: "prose",
104
+ heading: block.heading,
105
+ summary: block.content.trim(),
106
+ facts: undefined,
107
+ };
108
+ }
109
+
110
+ const lines = block.content.split("\n").filter((line) => line.trim());
111
+ const firstLine = lines[0] || "";
112
+ const remainingLines = lines.slice(1);
113
+
114
+ const isSummary =
115
+ !firstLine.startsWith("-") && !firstLine.startsWith("*") && !firstLine.startsWith("#");
116
+
117
+ return {
118
+ id: slugify(block.heading),
119
+ blockType: "prose",
120
+ heading: block.heading,
121
+ summary: isSummary ? firstLine : undefined,
122
+ facts: isSummary ? remainingLines : lines,
123
+ };
124
+ });
125
+ }
@@ -0,0 +1,92 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Price marker resolution for semantic projections (RFC-0767). Resolves {price:offering:chargeRef} markers to source-currency (EUR) strings for JSON-LD and meta tags. Also hosts the relocated DerivedPriceEntry type, OFFERING_URI_PREFIX constant, and PRICE_MARKER_RE regex shared between the semantic layer and packages/ui.</purpose>
4
+ <non-goals>
5
+ <item>Does not read files — derived prices are passed as a parameter. File I/O lives in derived-prices-loader.ts.</item>
6
+ <item>Does not append recurrence suffixes — JSON-LD headline/description are free-text strings where recurrence is in the surrounding sentence.</item>
7
+ <item>Does not resolve markers in block-derived content or prose body — only heading, lead, description (RFC-0767 nonGoals).</item>
8
+ </non-goals>
9
+ </MODULE_CONTRACT>
10
+ <CHANGE_SUMMARY>
11
+ <item>RFC-0767: relocated DerivedPriceEntry, OFFERING_URI_PREFIX, PRICE_MARKER_RE from packages/ui to packages/share to break circular dependency.</item>
12
+ <item>RFC-0767: added formatSourcePrice and resolvePriceMarkersForSemantic for semantic-layer marker resolution.</item>
13
+ </CHANGE_SUMMARY>
14
+ */
15
+
16
+ /**
17
+ * Derived price entry shape (moved from packages/ui to packages/share
18
+ * so both the semantic layer and UI components share a single type).
19
+ */
20
+ export interface DerivedPriceEntry {
21
+ chargeRef: string;
22
+ targetCurrency: string;
23
+ amount: { value: string; currency: string };
24
+ trace: {
25
+ source: { amount: string; currency: string };
26
+ rate: { value: string; pair: string };
27
+ calculation?: {
28
+ rounding?: {
29
+ mode: string;
30
+ increment?: string;
31
+ decimalPlaces?: number;
32
+ output: string;
33
+ };
34
+ };
35
+ };
36
+ }
37
+
38
+ export const OFFERING_URI_PREFIX = "https://warpgogol.com/id/offerings/";
39
+ export const PRICE_MARKER_RE = /\{price:([a-zA-Z0-9_-]+):([a-zA-Z0-9_.-]+)\}/g;
40
+ export const AMOUNT_MARKER_RE = /\{amount:([0-9]+(?:\.[0-9]+)?)\}/g;
41
+
42
+ /**
43
+ * Format a source-currency amount for semantic projections.
44
+ * Unlike formatPrice in packages/ui, this does NOT append a recurrence
45
+ * suffix — JSON-LD headline/description fields are free-text strings
46
+ * where the recurrence context is already in the surrounding sentence.
47
+ * Uses Intl.NumberFormat with currencyDisplay: "narrowSymbol".
48
+ * Output contains a non-breaking space (U+00A0) between number and symbol.
49
+ */
50
+ export function formatSourcePrice(amount: string, lang: string): string {
51
+ const numericAmount = Number(amount);
52
+ if (!Number.isFinite(numericAmount)) return "0\u00A0€";
53
+ return new Intl.NumberFormat(lang, {
54
+ style: "currency",
55
+ currency: "EUR",
56
+ currencyDisplay: "narrowSymbol",
57
+ minimumFractionDigits: 0,
58
+ maximumFractionDigits: 2,
59
+ }).format(numericAmount);
60
+ }
61
+
62
+ /**
63
+ * Resolve {price:offering:chargeRef} markers in a string to source-currency
64
+ * (EUR) formatted strings for use in semantic projections (JSON-LD, meta tags).
65
+ *
66
+ * Returns the input string unchanged if no markers are present.
67
+ * Returns "0 €" (with non-breaking space) for unknown offerings or chargeRefs
68
+ * (same fallback as parsePriceMarkers in packages/ui).
69
+ */
70
+ export function resolvePriceMarkersForSemantic(
71
+ text: string,
72
+ lang: string,
73
+ derivedPrices?: Record<string, DerivedPriceEntry[]> | null,
74
+ ): string {
75
+ return text.replace(PRICE_MARKER_RE, (_match, offeringId: string, chargeRef: string) => {
76
+ const ref = OFFERING_URI_PREFIX + offeringId;
77
+ const entry = derivedPrices?.[ref]?.find((e) => e.chargeRef === chargeRef);
78
+ const sourceAmount = entry?.trace?.source?.amount ?? "0";
79
+ return formatSourcePrice(sourceAmount, lang);
80
+ });
81
+ }
82
+
83
+ /**
84
+ * Resolve {amount:NNNN} markers to source-currency (EUR) formatted strings
85
+ * for semantic projections (JSON-LD, meta tags). These markers represent
86
+ * literal EUR amounts (e.g. thresholds) that are not tied to an offering.
87
+ */
88
+ export function resolveAmountMarkersForSemantic(text: string, lang: string): string {
89
+ return text.replace(AMOUNT_MARKER_RE, (_match, amount: string) => {
90
+ return formatSourcePrice(amount, lang);
91
+ });
92
+ }
@@ -0,0 +1,127 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>
4
+ RFC-0052 robots.txt generation. Pure function buildRobotsTxt produces the
5
+ robots exclusion standard file from a structured RobotsPolicy config.
6
+ </purpose>
7
+ <non-goals>
8
+ <item>Do not read content files or validate input — the caller is responsible.</item>
9
+ <item>Do not manage default policy fallback — caller decides skip vs fallback.</item>
10
+ </non-goals>
11
+ </MODULE_CONTRACT>
12
+ <CHANGE_SUMMARY>
13
+ <item>RFC-0052: Initial implementation.</item>
14
+ <item>RFC-0784: Add contentSignal field and Content-Signal directive output.</item>
15
+ </CHANGE_SUMMARY>
16
+ */
17
+
18
+ export interface RobotsRule {
19
+ userAgent: string;
20
+ disallow: string[];
21
+ allow: string[];
22
+ }
23
+
24
+ export interface RobotsPolicy {
25
+ defaultPolicy?: "allow" | "disallow";
26
+ disallowedPaths?: string[];
27
+ allowedPaths?: string[];
28
+ crawlerBlocklist?: string[];
29
+ crawlerAllowlist?: string[];
30
+ sitemap?: string;
31
+ customRules?: RobotsRule[];
32
+ /** RFC-0784: Content-Signal directive — AI usage preferences (ai-train, search, ai-input). */
33
+ contentSignal?: string[];
34
+ }
35
+
36
+ export function buildRobotsTxt(policy: RobotsPolicy): string {
37
+ const lines: string[] = [];
38
+
39
+ lines.push("# robots.txt");
40
+ lines.push(`# Generated by robots.generate (RFC-0052)`);
41
+ lines.push("");
42
+
43
+ // RFC-0784: Content-Signal directive — placed after header comment, before User-agent block.
44
+ if (policy.contentSignal && policy.contentSignal.length > 0) {
45
+ lines.push(`Content-Signal: ${policy.contentSignal.join(", ")}`);
46
+ lines.push("");
47
+ }
48
+
49
+ const defaultPolicy = policy.defaultPolicy ?? "allow";
50
+ const defaultDisallow = policy.disallowedPaths ?? [];
51
+ const defaultAllow = policy.allowedPaths ?? [];
52
+ const blocklist = policy.crawlerBlocklist ?? [];
53
+ const allowlist = policy.crawlerAllowlist ?? [];
54
+ const customRules = policy.customRules ?? [];
55
+
56
+ // Default User-agent group
57
+ lines.push("# Default crawl policy");
58
+ lines.push("User-agent: *");
59
+
60
+ if (defaultPolicy === "disallow") {
61
+ if (defaultDisallow.length === 0) {
62
+ lines.push("Disallow: /");
63
+ } else {
64
+ for (const path of defaultDisallow) {
65
+ lines.push(`Disallow: ${path}`);
66
+ }
67
+ }
68
+ } else if (defaultDisallow.length > 0 || defaultAllow.length > 0) {
69
+ // Default allow with specific overrides
70
+ for (const path of defaultAllow) {
71
+ lines.push(`Allow: ${path}`);
72
+ }
73
+ for (const path of defaultDisallow) {
74
+ lines.push(`Disallow: ${path}`);
75
+ }
76
+ }
77
+ // Default allow with no overrides — omit redundant "Allow: /" (PUBTXT-09);
78
+ // the auxiliary discovery files below are explicitly allowed.
79
+
80
+ // Auxiliary discovery files — explicitly allowed for all crawlers
81
+ lines.push("");
82
+ lines.push("# Auxiliary discovery files — explicitly allowed for all crawlers");
83
+ lines.push("Allow: /robots.txt");
84
+ lines.push("Allow: /llms.txt");
85
+ lines.push("Allow: /llms-full.txt");
86
+ lines.push("Allow: /sitemap.xml");
87
+ lines.push("Allow: /sitemap-content.xml");
88
+
89
+ // Crawler allowlist (friendly AI / search crawlers)
90
+ if (allowlist.length > 0) {
91
+ lines.push("");
92
+ lines.push("# Allow known friendly crawlers explicitly");
93
+ for (const agent of allowlist) {
94
+ lines.push(`User-agent: ${agent}`);
95
+ }
96
+ lines.push("Allow: /");
97
+ }
98
+
99
+ // Crawler blocklist
100
+ if (blocklist.length > 0) {
101
+ lines.push("");
102
+ lines.push("# Block known low-value crawlers");
103
+ for (const agent of blocklist) {
104
+ lines.push(`User-agent: ${agent}`);
105
+ }
106
+ lines.push("Disallow: /");
107
+ }
108
+
109
+ // Custom per-user-agent rules
110
+ for (const rule of customRules) {
111
+ lines.push("");
112
+ lines.push(`User-agent: ${rule.userAgent}`);
113
+ for (const path of rule.disallow) {
114
+ lines.push(`Disallow: ${path}`);
115
+ }
116
+ for (const path of rule.allow) {
117
+ lines.push(`Allow: ${path}`);
118
+ }
119
+ }
120
+
121
+ // Sitemap reference
122
+ const sitemap = policy.sitemap ?? "/sitemap.xml";
123
+ lines.push("");
124
+ lines.push(`Sitemap: ${sitemap}`);
125
+
126
+ return lines.join("\n") + "\n";
127
+ }
@@ -0,0 +1,136 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>
4
+ RFC-0317: source-backed update-stamp resolver. Provides a deterministic,
5
+ non-fabricated lastmod date for sitemap, feed, and Markdown twin provenance.
6
+ Never uses build date, current time, or filesystem mtime.
7
+ </purpose>
8
+ <non-goals>
9
+ <item>Do not use new Date(), build time, or file mtime as a stamp source.</item>
10
+ <item>Do not fabricate stamps when no source is available — return undefined.</item>
11
+ </non-goals>
12
+ </MODULE_CONTRACT>
13
+ <CHANGE_SUMMARY>
14
+ <item>RFC-0317: initial implementation — authored frontmatter + sitemap.lastmod + CKL ledger sources.</item>
15
+ </CHANGE_SUMMARY>
16
+ */
17
+
18
+ export type UpdateStampSource =
19
+ | "authored-page-frontmatter"
20
+ | "authored-system-output"
21
+ | "ckl-claim-ledger"
22
+ | "git-content-history";
23
+
24
+ export interface PageUpdateStamp {
25
+ date: string; // YYYY-MM-DD
26
+ source: UpdateStampSource;
27
+ inputs: string[]; // content files or records that contributed
28
+ }
29
+
30
+ export type UpdateStampMissingReason =
31
+ "not-authored" | "git-unavailable" | "insufficient-history" | "no-content-source";
32
+
33
+ export interface PageUpdateStampResult {
34
+ pageId: string;
35
+ lang: string;
36
+ stamp?: PageUpdateStamp;
37
+ missingReason?: UpdateStampMissingReason;
38
+ }
39
+
40
+ /** RFC-0317: validate a date string is YYYY-MM-DD. */
41
+ export function isValidStampDate(value: unknown): value is string {
42
+ if (typeof value !== "string") return false;
43
+ return /^\d{4}-\d{2}-\d{2}$/.test(value);
44
+ }
45
+
46
+ /**
47
+ * RFC-0317 priority 1: explicit authored content date.
48
+ * Checks `output.sitemap.lastmod` and `article.updatedAt` / `article.publishedAt`
49
+ * from the system.md page entry.
50
+ */
51
+ export function resolveAuthoredStamp(
52
+ pageEntry: Record<string, unknown>,
53
+ ): PageUpdateStamp | undefined {
54
+ const output = pageEntry.output as
55
+ | { sitemap?: { lastmod?: string }; article?: { updatedAt?: string; publishedAt?: string } }
56
+ | undefined;
57
+
58
+ const lastmod = output?.sitemap?.lastmod;
59
+ if (isValidStampDate(lastmod)) {
60
+ return {
61
+ date: lastmod,
62
+ source: "authored-system-output",
63
+ inputs: ["system.md:output.sitemap.lastmod"],
64
+ };
65
+ }
66
+
67
+ const article =
68
+ (pageEntry.article as { updatedAt?: string; publishedAt?: string } | undefined) ??
69
+ output?.article;
70
+ if (article) {
71
+ if (isValidStampDate(article.updatedAt)) {
72
+ return {
73
+ date: article.updatedAt,
74
+ source: "authored-page-frontmatter",
75
+ inputs: ["system.md:article.updatedAt"],
76
+ };
77
+ }
78
+ if (isValidStampDate(article.publishedAt)) {
79
+ return {
80
+ date: article.publishedAt,
81
+ source: "authored-page-frontmatter",
82
+ inputs: ["system.md:article.publishedAt"],
83
+ };
84
+ }
85
+ }
86
+
87
+ return undefined;
88
+ }
89
+
90
+ /**
91
+ * RFC-0317 priority 2: CKL claim ledger dates.
92
+ * Given a list of ledger asOf dates that match the page's business domain,
93
+ * return the latest one as a stamp.
94
+ */
95
+ export function resolveCklStamp(
96
+ ledgerDates: string[],
97
+ inputs: string[],
98
+ ): PageUpdateStamp | undefined {
99
+ const valid = ledgerDates.filter(isValidStampDate);
100
+ if (valid.length === 0) return undefined;
101
+ const latest = [...valid].sort().at(-1)!;
102
+ return {
103
+ date: latest,
104
+ source: "ckl-claim-ledger",
105
+ inputs,
106
+ };
107
+ }
108
+
109
+ /**
110
+ * RFC-0317: resolve a single page's update stamp.
111
+ * Tries authored sources first, then CKL ledger if provided.
112
+ * Returns `stamp: undefined` when no allowed source is available.
113
+ */
114
+ export function resolvePageUpdateStamp(params: {
115
+ pageId: string;
116
+ lang: string;
117
+ pageEntry: Record<string, unknown>;
118
+ cklLedgerDates?: string[];
119
+ cklInputs?: string[];
120
+ }): PageUpdateStampResult {
121
+ const { pageId, lang, pageEntry, cklLedgerDates, cklInputs } = params;
122
+
123
+ const authored = resolveAuthoredStamp(pageEntry);
124
+ if (authored) {
125
+ return { pageId, lang, stamp: authored };
126
+ }
127
+
128
+ if (cklLedgerDates && cklLedgerDates.length > 0) {
129
+ const ckl = resolveCklStamp(cklLedgerDates, cklInputs ?? []);
130
+ if (ckl) {
131
+ return { pageId, lang, stamp: ckl };
132
+ }
133
+ }
134
+
135
+ return { pageId, lang, missingReason: "no-content-source" };
136
+ }
@@ -0,0 +1,177 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>
4
+ Implements RFC-0099 page-driven shared component context resolution.
5
+ Provides app-agnostic helpers that merge block props from other pages using
6
+ priority order derived from system.md.
7
+ </purpose>
8
+ <non-goals>
9
+ <item>Do not read files or Astro collections directly.</item>
10
+ <item>Do not validate propsSchema correctness.</item>
11
+ </non-goals>
12
+ </MODULE_CONTRACT>
13
+ <CHANGE_SUMMARY>
14
+ <item>RFC-0099: Added shared page-driven context fallback helpers.</item>
15
+ </CHANGE_SUMMARY>
16
+ */
17
+
18
+ import { deepMergeEntryData } from "./content/merge.ts";
19
+
20
+ export interface SharedContextBlockLike {
21
+ id?: string;
22
+ type?: string;
23
+ use?: string;
24
+ props?: Record<string, unknown>;
25
+ }
26
+
27
+ export interface SharedContextPageLike {
28
+ pageId: string;
29
+ blocks: SharedContextBlockLike[];
30
+ }
31
+
32
+ export interface SharedContextCandidate {
33
+ pageId: string;
34
+ block: SharedContextBlockLike;
35
+ }
36
+
37
+ export interface SharedContextCandidatesByLevel {
38
+ home: SharedContextCandidate[];
39
+ required: SharedContextCandidate[];
40
+ other: SharedContextCandidate[];
41
+ }
42
+
43
+ function isRecord(value: unknown): value is Record<string, unknown> {
44
+ return typeof value === "object" && value !== null && !Array.isArray(value);
45
+ }
46
+
47
+ function getBlockSelector(block: SharedContextBlockLike): string | null {
48
+ return block.type ?? block.use ?? null;
49
+ }
50
+
51
+ function matchesSharedBlock(
52
+ source: SharedContextBlockLike,
53
+ target: SharedContextBlockLike,
54
+ ): boolean {
55
+ if (!source.id || !target.id || source.id !== target.id) {
56
+ return false;
57
+ }
58
+
59
+ const sourceSelector = getBlockSelector(source);
60
+ const targetSelector = getBlockSelector(target);
61
+
62
+ if (sourceSelector && targetSelector) {
63
+ return sourceSelector === targetSelector;
64
+ }
65
+
66
+ return true;
67
+ }
68
+
69
+ export function buildSharedContextPageOrder(options: {
70
+ currentPageId: string;
71
+ allPageIds: string[];
72
+ requiredPageIds?: string[];
73
+ homePageId?: string;
74
+ }): string[] {
75
+ const { currentPageId, allPageIds, requiredPageIds = [], homePageId = "home" } = options;
76
+
77
+ const remaining = allPageIds.filter((pageId) => pageId !== currentPageId);
78
+ const ordered: string[] = [];
79
+
80
+ if (remaining.includes(homePageId)) {
81
+ ordered.push(homePageId);
82
+ }
83
+
84
+ for (const pageId of requiredPageIds) {
85
+ if (pageId !== currentPageId && pageId !== homePageId && remaining.includes(pageId)) {
86
+ ordered.push(pageId);
87
+ }
88
+ }
89
+
90
+ for (const pageId of remaining) {
91
+ if (!ordered.includes(pageId)) {
92
+ ordered.push(pageId);
93
+ }
94
+ }
95
+
96
+ return ordered;
97
+ }
98
+
99
+ export function collectSharedContextCandidatesByLevel(options: {
100
+ currentPageId: string;
101
+ block: SharedContextBlockLike;
102
+ pages: Map<string, SharedContextPageLike>;
103
+ requiredPageIds?: string[];
104
+ homePageId?: string;
105
+ }): SharedContextCandidatesByLevel {
106
+ const { currentPageId, block, pages, requiredPageIds = [], homePageId = "home" } = options;
107
+
108
+ const result: SharedContextCandidatesByLevel = {
109
+ home: [],
110
+ required: [],
111
+ other: [],
112
+ };
113
+
114
+ if (!block.id) {
115
+ return result;
116
+ }
117
+
118
+ const allPageIds = [...pages.keys()];
119
+ const ordered = buildSharedContextPageOrder({
120
+ currentPageId,
121
+ allPageIds,
122
+ requiredPageIds,
123
+ homePageId,
124
+ });
125
+
126
+ for (const pageId of ordered) {
127
+ const page = pages.get(pageId);
128
+ if (!page) continue;
129
+
130
+ const candidateBlock = page.blocks.find((sourceBlock) =>
131
+ matchesSharedBlock(sourceBlock, block),
132
+ );
133
+ if (!candidateBlock) continue;
134
+
135
+ const candidate = { pageId, block: candidateBlock };
136
+
137
+ if (pageId === homePageId) {
138
+ result.home.push(candidate);
139
+ } else if (requiredPageIds.includes(pageId)) {
140
+ result.required.push(candidate);
141
+ } else {
142
+ result.other.push(candidate);
143
+ }
144
+ }
145
+
146
+ return result;
147
+ }
148
+
149
+ export function resolveSharedContextProps(options: {
150
+ currentPageId: string;
151
+ block: SharedContextBlockLike;
152
+ pages: Map<string, SharedContextPageLike>;
153
+ requiredPageIds?: string[];
154
+ homePageId?: string;
155
+ }): Record<string, unknown> {
156
+ const { currentPageId, block, pages, requiredPageIds = [], homePageId = "home" } = options;
157
+
158
+ const explicitProps = isRecord(block.props) ? block.props : {};
159
+ if (!block.id) {
160
+ return explicitProps;
161
+ }
162
+
163
+ const candidates = collectSharedContextCandidatesByLevel({
164
+ currentPageId,
165
+ block,
166
+ pages,
167
+ requiredPageIds,
168
+ homePageId,
169
+ });
170
+
171
+ const source = candidates.home[0] ?? candidates.required[0] ?? candidates.other[0];
172
+ if (!source || !isRecord(source.block.props)) {
173
+ return explicitProps;
174
+ }
175
+
176
+ return deepMergeEntryData(source.block.props, explicitProps);
177
+ }
@@ -0,0 +1,19 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Maintains packages/share/src/string-utils.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>Dedup: centralise the kebab-case helper previously duplicated in site-kernel and site-kernel-checks.</item>
10
+ </CHANGE_SUMMARY>
11
+ */
12
+
13
+ /** Lowercase, dash-only kebab-case. Strips leading/trailing hyphens. */
14
+ export function toKebabCase(value: string): string {
15
+ return value
16
+ .toLowerCase()
17
+ .replace(/[^a-z0-9]+/g, "-")
18
+ .replace(/^-+|-+$/g, "");
19
+ }