pi-apexlang 0.2.2 → 0.4.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 (239) hide show
  1. package/README.md +22 -5
  2. package/THIRD_PARTY_NOTICES.md +2 -2
  3. package/UPSTREAM.json +2 -2
  4. package/docs/assets/overview.en.png +0 -0
  5. package/docs/assets/overview.en.svg +89 -0
  6. package/docs/assets/overview.uk.png +0 -0
  7. package/docs/assets/overview.uk.svg +89 -0
  8. package/docs/en/README.md +41 -0
  9. package/docs/en/architecture.md +73 -0
  10. package/docs/en/getting-started.md +77 -0
  11. package/docs/en/maintenance.md +83 -0
  12. package/docs/en/tool-reference.md +85 -0
  13. package/docs/uk/README.md +41 -0
  14. package/docs/uk/architecture.md +73 -0
  15. package/docs/uk/getting-started.md +77 -0
  16. package/docs/uk/maintenance.md +83 -0
  17. package/docs/uk/tool-reference.md +85 -0
  18. package/extensions/apexlang/index.ts +19 -10
  19. package/extensions/lib/apexlang-cli.d.mts +1 -2
  20. package/extensions/lib/apexlang-cli.mjs +18 -13
  21. package/extensions/lib/apexlang-local-validate.mjs +83 -0
  22. package/extensions/lib/apexlang-local-validator.py +116 -0
  23. package/extensions/lib/apexlang-runtime-validate.mjs +76 -0
  24. package/package.json +2 -1
  25. package/skills/apexlang/APEXlang Skills and Workflows.md +325 -0
  26. package/skills/apexlang/README.md +6 -6
  27. package/skills/apexlang/RELEASE-NOTES.md +2 -2
  28. package/skills/apexlang/SKILL.md +24 -19
  29. package/skills/apexlang/agents/openai.yaml +4 -3
  30. package/skills/apexlang/assets/apex-generation/components.registry.json +1001 -16
  31. package/skills/apexlang/assets/apexlang/domains-catalog.json +6 -1
  32. package/skills/apexlang/assets/component-attributes.json +1924 -180
  33. package/skills/apexlang/assets/component-policies.json +19 -10
  34. package/skills/apexlang/assets/contracts/grammar-semantic-rules.json +134 -0
  35. package/skills/apexlang/assets/contracts/package-layers.json +56 -0
  36. package/skills/apexlang/assets/contracts/page-construction-packs.json +154 -0
  37. package/skills/apexlang/assets/contracts/page-patterns.json +119 -0
  38. package/skills/apexlang/assets/grammar/apexlang.ebnf +11743 -0
  39. package/skills/apexlang/assets/indexes/font-apex-icon-index.json +1699 -0
  40. package/skills/apexlang/assets/routing-catalog-main.json +11 -10
  41. package/skills/apexlang/assets/routing-load-policy.json +35 -9
  42. package/skills/apexlang/assets/rules-mapping.json +110 -2
  43. package/skills/apexlang/assets/rules.catalog.json +1298 -0
  44. package/skills/apexlang/assets/validator-fix-recipes.json +372 -17
  45. package/skills/apexlang/assets/workspace-intelligence.json +7 -10
  46. package/skills/apexlang/manifest.json +268 -12
  47. package/skills/apexlang/references/domains/README.md +1 -1
  48. package/skills/apexlang/references/domains/page-components/buttons.md +2 -2
  49. package/skills/apexlang/references/domains/page-components/page-items.md +3 -3
  50. package/skills/apexlang/references/domains/page-components/regions/region-display-selector/workflow-region-display-selector.md +69 -0
  51. package/skills/apexlang/references/domains/page-components/regions.md +1 -0
  52. package/skills/apexlang/references/domains/template-components/comments.md +61 -0
  53. package/skills/apexlang/references/domains/template-components/registry.md +2 -0
  54. package/skills/apexlang/references/domains/template-components/templates.md +47 -0
  55. package/skills/apexlang/references/domains/template-components/timeline.md +51 -0
  56. package/skills/apexlang/references/ops/one-message-router-contract.md +2 -2
  57. package/skills/apexlang/references/ops/runtime-gates/01-direct-sqlcl-import.md +1 -1
  58. package/skills/apexlang/references/ops/runtime-gates/02-direct-sqlcl-validate-gate.md +1 -1
  59. package/skills/apexlang/references/ops/runtime-gates.md +17 -16
  60. package/skills/apexlang/references/ops/sqlcl-agents/00-connection-gate.md +7 -7
  61. package/skills/apexlang/references/ops/sqlcl.md +7 -8
  62. package/skills/apexlang/references/policies/compiler-prop-map.md +6 -4
  63. package/skills/apexlang/references/policies/governance/00-governance.md +2 -2
  64. package/skills/apexlang/references/policies/memory-bank/00-guard/ai.guard.md +126 -922
  65. package/skills/apexlang/references/policies/memory-bank/20-data/apex.logic.md +3 -3
  66. package/skills/apexlang/references/policies/memory-bank/20-data/db.connection.md +9 -11
  67. package/skills/apexlang/references/policies/memory-bank/30-pages/apex.cards-page.md +95 -0
  68. package/skills/apexlang/references/policies/memory-bank/30-pages/apex.page.md +2 -2
  69. package/skills/apexlang/references/policies/memory-bank/30-pages/apex.smart-filter-search.md +145 -74
  70. package/skills/apexlang/references/policies/memory-bank/40-components/apex.region-interactions.md +9 -2
  71. package/skills/apexlang/references/policies/memory-bank/40-components/apex.region-media.md +6 -5
  72. package/skills/apexlang/references/policies/memory-bank/systemPatterns.md +9 -1
  73. package/skills/apexlang/references/workflows/apex-generation/agents/20-agent-draft.md +51 -232
  74. package/skills/apexlang/references/workflows/apex-generation/agents/30-agent-critique.md +49 -449
  75. package/skills/apexlang/references/workflows/apex-generation/agents/40-agent-revision.md +38 -238
  76. package/skills/apexlang/references/workflows/apex-generation/nlu/nlu-routing-test-matrix.md +8 -2
  77. package/skills/apexlang/references/workflows/apex-generation/nlu/nlu-routing-validation-notes.md +19 -3
  78. package/skills/apexlang/references/workflows/apex-generation/registry.md +15 -1
  79. package/skills/apexlang/references/workflows/apex-generation/templates.md +1 -1
  80. package/skills/apexlang/references/workflows/apex-generation.md +1 -1
  81. package/skills/apexlang/references/workflows/apexlang/apexlang-execution-model.md +5 -5
  82. package/skills/apexlang/references/workflows/apexlang/prompt-contracts.md +81 -10
  83. package/skills/apexlang/references/workflows/apexlang/workflow-create-app-from-fr-and-model.md +18 -7
  84. package/skills/apexlang/release-notes.json +156 -0
  85. package/skills/apexlang/runtime/grammar_contract.mjs +1166 -0
  86. package/skills/apexlang/runtime/internal/python/format_apexlang.py +210 -0
  87. package/skills/apexlang/runtime/internal/python/validate_apexlang.py +14142 -5843
  88. package/skills/apexlang/runtime/internal/python/validator_common.py +2 -0
  89. package/skills/apexlang/runtime/lib/catalogs.mjs +1026 -0
  90. package/skills/apexlang/runtime/lib/common.mjs +35 -0
  91. package/skills/apexlang/runtime/runtime.bundle.mjs +457 -57
  92. package/skills/apexlang/templates/items/combobox/combobox.explicit-defaults.md +1 -1
  93. package/skills/apexlang/templates/items/date-picker/date-picker._common.md +1 -1
  94. package/skills/apexlang/templates/items/date-picker/date-picker.explicit-defaults.md +1 -1
  95. package/skills/apexlang/templates/items/file-upload/file-upload._common.md +30 -24
  96. package/skills/apexlang/templates/items/file-upload/file-upload.compact-template.md +6 -0
  97. package/skills/apexlang/templates/items/file-upload/file-upload.explicit-defaults.md +23 -8
  98. package/skills/apexlang/templates/items/file-upload/file-upload.minimal.md +6 -0
  99. package/skills/apexlang/templates/items/hidden-item/hidden-item._common.md +1 -1
  100. package/skills/apexlang/templates/items/hidden-item/hidden-item.explicit-defaults.md +1 -1
  101. package/skills/apexlang/templates/items/items._common.md +3 -2
  102. package/skills/apexlang/templates/items/list-manager/list-manager.explicit-defaults.md +1 -1
  103. package/skills/apexlang/templates/items/markdown-editor/markdown-editor._common.md +1 -1
  104. package/skills/apexlang/templates/items/markdown-editor/markdown-editor.explicit-defaults.md +1 -1
  105. package/skills/apexlang/templates/items/number-field/number-field._common.md +8 -6
  106. package/skills/apexlang/templates/items/number-field/number-field.explicit-defaults.md +12 -2
  107. package/skills/apexlang/templates/items/popup-lov/popup-lov.explicit-defaults.md +1 -1
  108. package/skills/apexlang/templates/items/rich-text-editor/rich-text-editor._common.md +1 -1
  109. package/skills/apexlang/templates/items/rich-text-editor/rich-text-editor.explicit-defaults.md +1 -1
  110. package/skills/apexlang/templates/items/select-many/standard.md +1 -1
  111. package/skills/apexlang/templates/items/shuttle/shuttle.explicit-defaults.md +1 -1
  112. package/skills/apexlang/templates/items/switch/switch._common.md +8 -8
  113. package/skills/apexlang/templates/items/switch/switch.explicit-defaults.md +1 -1
  114. package/skills/apexlang/templates/items/text-area/text-area._common.md +2 -3
  115. package/skills/apexlang/templates/items/text-area/text-area.explicit-defaults.md +1 -1
  116. package/skills/apexlang/templates/items/text-area/text-area.required-length.md +1 -3
  117. package/skills/apexlang/templates/items/text-autocomplete/text-autocomplete.explicit-defaults.md +1 -1
  118. package/skills/apexlang/templates/items/text-autocomplete/text-autocomplete.required-length.md +1 -3
  119. package/skills/apexlang/templates/items/text-field/text-field._common.md +2 -3
  120. package/skills/apexlang/templates/items/text-field/text-field.explicit-defaults.md +1 -1
  121. package/skills/apexlang/templates/items/text-field/text-field.required-length.md +1 -3
  122. package/skills/apexlang/templates/page-examples/faceted-search/faceted-search.example.md +0 -5
  123. package/skills/apexlang/templates/page-examples/form-page/form-page.example.md +0 -9
  124. package/skills/apexlang/templates/page-examples/interactive-report-page/interactive-report-page.example.md +2 -2
  125. package/skills/apexlang/templates/page-examples/login-page/login-page.example.md +0 -8
  126. package/skills/apexlang/templates/page-examples/task-definition-initiated-tasks-page/task-definition-initiated-tasks-page.example.md +7 -17
  127. package/skills/apexlang/templates/page-examples/task-definition-my-tasks-page/task-definition-my-tasks-page.example.md +7 -65
  128. package/skills/apexlang/templates/page-examples/task-definition-task-details/task-definition-task-details.example.md +38 -201
  129. package/skills/apexlang/templates/region-components/calendar/calendar.custom-styling.md +3 -1
  130. package/skills/apexlang/templates/region-components/calendar/calendar.faceted-search.md +3 -2
  131. package/skills/apexlang/templates/region-components/cards/README.md +12 -4
  132. package/skills/apexlang/templates/region-components/cards/cards._common.md +176 -37
  133. package/skills/apexlang/templates/region-components/cards/cards._index.md +2 -1
  134. package/skills/apexlang/templates/region-components/cards/cards.refresh-after-dialog.md +46 -0
  135. package/skills/apexlang/templates/region-components/cards/cards.refresh-on-change.md +58 -0
  136. package/skills/apexlang/templates/region-components/cards/cards.rest-source.md +54 -18
  137. package/skills/apexlang/templates/region-components/cards/cards.standard.md +37 -21
  138. package/skills/apexlang/templates/region-components/chart/chart._series._common.md +1 -4
  139. package/skills/apexlang/templates/region-components/chart/chart.pie.md +6 -2
  140. package/skills/apexlang/templates/region-components/chart/chart.status-meter-gauge.md +1 -1
  141. package/skills/apexlang/templates/region-components/chart/chart.stock.md +2 -0
  142. package/skills/apexlang/templates/region-components/chart/features/chart.feature-chart-links.md +9 -7
  143. package/skills/apexlang/templates/region-components/faceted-search/faceted-search._common.md +1 -14
  144. package/skills/apexlang/templates/region-components/form/form._items._common.md +1 -5
  145. package/skills/apexlang/templates/region-components/interactive-report/interactive-report._columns._common.md +2 -6
  146. package/skills/apexlang/templates/region-components/interactive-report/interactive-report.page-header-actions.md +1 -7
  147. package/skills/apexlang/templates/region-components/list/README.md +1 -1
  148. package/skills/apexlang/templates/region-components/list/list._common.md +1 -1
  149. package/skills/apexlang/templates/region-components/map/map.backgrounds.md +12 -10
  150. package/skills/apexlang/templates/region-components/map/map.layer.extruded-polygons.md +16 -12
  151. package/skills/apexlang/templates/region-components/map/map.layer.heat-map.md +10 -7
  152. package/skills/apexlang/templates/region-components/map/map.layer.lines.md +10 -8
  153. package/skills/apexlang/templates/region-components/map/map.layer.polygons.md +21 -18
  154. package/skills/apexlang/templates/region-components/map/map.region.background-custom.md +25 -19
  155. package/skills/apexlang/templates/region-components/map/map.region.background-shared.md +19 -13
  156. package/skills/apexlang/templates/region-components/map/map.region.bbox-sql.md +13 -13
  157. package/skills/apexlang/templates/region-components/map/map.region.bbox-static.md +2 -2
  158. package/skills/apexlang/templates/region-components/map/map.region.browser-location.md +3 -7
  159. package/skills/apexlang/templates/region-components/map/map.region.init-position-sql.md +7 -7
  160. package/skills/apexlang/templates/region-components/region-display-selector/README.md +5 -2
  161. package/skills/apexlang/templates/region-components/region-display-selector/region-display-selector._common.md +60 -7
  162. package/skills/apexlang/templates/region-components/region-display-selector/region-display-selector._index.md +4 -1
  163. package/skills/apexlang/templates/region-components/region-display-selector/region-display-selector.permutations.md +55 -0
  164. package/skills/apexlang/templates/region-components/region-display-selector/region-display-selector.standard.md +91 -5
  165. package/skills/apexlang/templates/region-components/smart-filter-search/README.md +6 -2
  166. package/skills/apexlang/templates/region-components/smart-filter-search/smart-filter-search._common.md +107 -46
  167. package/skills/apexlang/templates/region-components/smart-filter-search/smart-filter-search._index.md +4 -3
  168. package/skills/apexlang/templates/region-components/smart-filter-search/smart-filter-search.standard.md +33 -17
  169. package/skills/apexlang/templates/shared-components/component-settings/component-settings._common.md +1 -0
  170. package/skills/apexlang/templates/shared-components/component-settings.example.md +1 -0
  171. package/skills/apexlang/templates/shared-components/email-templates.example.md +1 -1
  172. package/skills/apexlang/templates/shared-components/rest-data-sources/example_rest_data_source.example.md +40 -46
  173. package/skills/apexlang/templates/shared-components/task-definitions/action-task.example.md +2 -30
  174. package/skills/apexlang/templates/shared-components/task-definitions/approve-request.example.md +3 -31
  175. package/skills/apexlang/templates/template-components/README.md +27 -15
  176. package/skills/apexlang/templates/template-components/avatar/README.md +56 -0
  177. package/skills/apexlang/templates/template-components/avatar/avatar._common.md +149 -0
  178. package/skills/apexlang/templates/template-components/avatar/avatar._index.md +42 -0
  179. package/skills/apexlang/templates/template-components/{avatar._template_options.md → avatar/avatar._template_options.md} +1 -1
  180. package/skills/apexlang/templates/template-components/avatar/avatar.report-icon.md +48 -0
  181. package/skills/apexlang/templates/template-components/avatar/avatar.report-image-url-column.md +62 -0
  182. package/skills/apexlang/templates/template-components/avatar/avatar.report-initials.md +54 -0
  183. package/skills/apexlang/templates/template-components/badge/README.md +38 -0
  184. package/skills/apexlang/templates/template-components/badge/badge._common.md +183 -0
  185. package/skills/apexlang/templates/template-components/badge/badge._index.md +20 -0
  186. package/skills/apexlang/templates/template-components/badge/badge.report-link.md +66 -0
  187. package/skills/apexlang/templates/template-components/badge/badge.report-minimal.md +76 -0
  188. package/skills/apexlang/templates/template-components/comments/README.md +20 -0
  189. package/skills/apexlang/templates/template-components/comments/comments._common.md +43 -0
  190. package/skills/apexlang/templates/template-components/comments/comments._index.md +25 -0
  191. package/skills/apexlang/templates/template-components/{comments._template_options.md → comments/comments._template_options.md} +2 -1
  192. package/skills/apexlang/templates/template-components/comments/comments.apex-task-comments.md +77 -0
  193. package/skills/apexlang/templates/template-components/comments/comments.partial-column.md +120 -0
  194. package/skills/apexlang/templates/template-components/comments/comments.permutations.md +425 -0
  195. package/skills/apexlang/templates/template-components/content-row/README.md +1 -1
  196. package/skills/apexlang/templates/template-components/content-row/content-row._common.md +1 -1
  197. package/skills/apexlang/templates/template-components/content-row/content-row._index.md +1 -1
  198. package/skills/apexlang/templates/template-components/content-row/content-row._template_options.md +1 -1
  199. package/skills/apexlang/templates/template-components/flexbox-container/README.md +9 -0
  200. package/skills/apexlang/templates/template-components/flexbox-container/flexbox-container._index.md +13 -0
  201. package/skills/apexlang/templates/template-components/media-list/README.md +100 -0
  202. package/skills/apexlang/templates/template-components/media-list/media-list._common.md +174 -0
  203. package/skills/apexlang/templates/template-components/media-list/media-list._index.md +50 -0
  204. package/skills/apexlang/templates/template-components/{media-list._template_options.md → media-list/media-list._template_options.md} +8 -2
  205. package/skills/apexlang/templates/template-components/media-list/media-list.partial.md +50 -0
  206. package/skills/apexlang/templates/template-components/media-list/media-list.report-avatar-badge.md +76 -0
  207. package/skills/apexlang/templates/template-components/media-list/media-list.report-base.md +71 -0
  208. package/skills/apexlang/templates/template-components/media-list/media-list.report-grouped.md +40 -0
  209. package/skills/apexlang/templates/template-components/media-list/media-list.report-layouts.md +50 -0
  210. package/skills/apexlang/templates/template-components/media-list/media-list.report-link.md +53 -0
  211. package/skills/apexlang/templates/template-components/metric-card/README.md +19 -4
  212. package/skills/apexlang/templates/template-components/metric-card/metric-card._common.md +122 -29
  213. package/skills/apexlang/templates/template-components/metric-card/metric-card._index.md +6 -0
  214. package/skills/apexlang/templates/template-components/metric-card/metric-card._template_options.md +47 -2
  215. package/skills/apexlang/templates/template-components/metric-card/metric-card.partial-minimal.md +51 -0
  216. package/skills/apexlang/templates/template-components/metric-card/metric-card.permutations.md +56 -0
  217. package/skills/apexlang/templates/template-components/metric-card/metric-card.report-avatar-badge.md +98 -0
  218. package/skills/apexlang/templates/template-components/metric-card/metric-card.report-grouping-selection.md +131 -0
  219. package/skills/apexlang/templates/template-components/metric-card/metric-card.report-link.md +66 -0
  220. package/skills/apexlang/templates/template-components/metric-card/metric-card.report-minimal.md +97 -0
  221. package/skills/apexlang/templates/template-components/template-components.registry.json +90 -1
  222. package/skills/apexlang/templates/template-components/timeline/README.md +19 -0
  223. package/skills/apexlang/templates/template-components/timeline/timeline._common.md +43 -0
  224. package/skills/apexlang/templates/template-components/timeline/timeline._index.md +24 -0
  225. package/skills/apexlang/templates/template-components/{timeline._template_options.md → timeline/timeline._template_options.md} +2 -1
  226. package/skills/apexlang/templates/template-components/timeline/timeline.permutations.md +399 -0
  227. package/skills/apexlang/templates/template-components/timeline/timeline.report-sample-data.md +168 -0
  228. package/skills/apexlang/templates/workspace-components/credentials/credentials-for-open-ai.example.md +1 -1
  229. package/skills/apexlang/templates/workspace-components/generative-ai-services/open-ai.example.md +8 -6
  230. package/skills/apexlang/tools/apexctl.mjs +167 -4
  231. package/skills/apexlang/tools/compiler-truth-audit.mjs +95 -63
  232. package/skills/apexlang/tools/query-valid-props-context.mjs +30 -0
  233. package/skills/apexlang/tools/query-valid-props-normalize.mjs +1 -0
  234. package/skills/apexlang/tools/query-valid-props-runtime.mjs +13 -8
  235. package/skills/apexlang/tools/query-valid-props-semantics.mjs +391 -0
  236. package/skills/apexlang/tools/query-valid-props-template-components.mjs +15 -1
  237. package/skills/apexlang/tools/query-valid-props.mjs +198 -216
  238. /package/skills/apexlang/templates/template-components/{badge._template_options.md → badge/badge._template_options.md} +0 -0
  239. /package/skills/apexlang/templates/template-components/{flexbox-container._template_options.md → flexbox-container/flexbox-container._template_options.md} +0 -0
@@ -0,0 +1,1166 @@
1
+ /**
2
+ * Build compact, task-scoped contracts from the canonical APEXlang EBNF.
3
+ */
4
+ import { createHash } from "node:crypto";
5
+ import { existsSync } from "node:fs";
6
+ import { promises as fs } from "node:fs";
7
+ import { fileURLToPath, pathToFileURL } from "node:url";
8
+ import path from "node:path";
9
+
10
+ const CONTRACT_SCHEMA_VERSION = 5;
11
+ const DEFAULT_CORE_PRODUCTIONS = [
12
+ "nl",
13
+ "blank-lines",
14
+ "ws",
15
+ "required-ws",
16
+ "indent",
17
+ "line-end",
18
+ "value",
19
+ "string-like-value",
20
+ "component-id",
21
+ "identifier",
22
+ "identifier-start",
23
+ "identifier-rest",
24
+ "string",
25
+ "multiline-string",
26
+ "reference",
27
+ "boolean",
28
+ "number",
29
+ "array",
30
+ "comment"
31
+ ];
32
+ const COMPILER_VALUE_FALLBACKS = new Map([
33
+ ["normal", "NORMAL"],
34
+ ["modalDialog", "MODAL"],
35
+ ["nonModalDialog", "NON_MODAL"],
36
+ ["breadcrumb", "NATIVE_BREADCRUMB"],
37
+ ["staticContent", "NATIVE_STATIC"],
38
+ ["classicReport", "NATIVE_SQL_REPORT"],
39
+ ["interactiveReport", "NATIVE_IR"],
40
+ ["form", "NATIVE_FORM"],
41
+ ["standard", "STANDARD"],
42
+ ["redirectThisApp", "REDIRECT_PAGE"],
43
+ ["databaseColumn", "DB_COLUMN"],
44
+ ["true", "Y"],
45
+ ["false", "N"]
46
+ ]);
47
+
48
+ const COMPILER_TO_DSL_PROPERTY_ALIASES = new Map([
49
+ ["newCol", "newColumn"],
50
+ ["databaseCol", "databaseColumn"]
51
+ ]);
52
+
53
+ export function normalizeCompilerPropertyName(propertyName) {
54
+ const normalized = String(propertyName ?? "").trim();
55
+ return COMPILER_TO_DSL_PROPERTY_ALIASES.get(normalized) || normalized;
56
+ }
57
+
58
+ function normalizeCompilerDslValue(property, value) {
59
+ const normalized = String(value ?? "").trim();
60
+ if (String(property?.propertyId ?? "") === "94" && normalized.startsWith("themeTemplateComponent/")) {
61
+ return `TMPL_${normalized.slice("themeTemplateComponent/".length)}`;
62
+ }
63
+ return normalized;
64
+ }
65
+ const INSTANCE_POLICY_RULES = [
66
+ {
67
+ id: "VALIDATION_CANONICAL_STRUCTURE_001",
68
+ family: "page.validation",
69
+ declarationIdPrefix: "VAL_",
70
+ forbidBlocks: ["config"],
71
+ requiredBlocks: ["execution", "validation", "error"],
72
+ requiredProperties: ["execution.sequence", "error.errorMessage"],
73
+ bindings: [
74
+ {
75
+ whenAssumptionEndsWith: "validation.item",
76
+ target: "error.associatedItem",
77
+ transform: "reference"
78
+ }
79
+ ]
80
+ }
81
+ ];
82
+
83
+ function resolveLayout() {
84
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
85
+ const packagedRoot = path.resolve(moduleDir, "..");
86
+ if (existsSync(path.join(packagedRoot, "assets", "grammar", "apexlang.ebnf"))) {
87
+ return {
88
+ packaged: true,
89
+ root: packagedRoot,
90
+ grammarPath: path.join(packagedRoot, "assets", "grammar", "apexlang.ebnf"),
91
+ semanticRulesPath: path.join(packagedRoot, "assets", "contracts", "grammar-semantic-rules.json")
92
+ };
93
+ }
94
+ const repoRoot = path.resolve(moduleDir, "../../../..");
95
+ return {
96
+ packaged: false,
97
+ root: repoRoot,
98
+ grammarPath: path.join(repoRoot, "references/policies", "apexlang", "grammar", "apexlang.ebnf"),
99
+ semanticRulesPath: path.join(repoRoot, "skills", "apexlang", "contracts", "grammar-semantic-rules.json")
100
+ };
101
+ }
102
+
103
+ async function ensureDir(directoryPath) {
104
+ await fs.mkdir(directoryPath, { recursive: true });
105
+ }
106
+
107
+ function parseProductions(grammarText) {
108
+ const productions = new Map();
109
+ let currentName = "";
110
+ let currentLines = [];
111
+
112
+ const flush = () => {
113
+ if (currentName) {
114
+ productions.set(currentName, currentLines.join("\n").trimEnd());
115
+ }
116
+ };
117
+
118
+ for (const line of grammarText.split(/\r?\n/)) {
119
+ const match = line.match(/^<([^>]+)>\s*::=/);
120
+ if (match) {
121
+ flush();
122
+ currentName = match[1];
123
+ currentLines = [line];
124
+ } else if (currentName) {
125
+ currentLines.push(line);
126
+ }
127
+ }
128
+ flush();
129
+ return productions;
130
+ }
131
+
132
+ function referencedProductions(definition) {
133
+ return [...definition.matchAll(/<([^>]+)>/g)].map((match) => match[1]);
134
+ }
135
+
136
+ function isChildSelector(name) {
137
+ return name.endsWith("-child-component");
138
+ }
139
+
140
+ function isGroupSelector(name) {
141
+ return name.endsWith("-group-block");
142
+ }
143
+
144
+ function selectorOptions(name, definition) {
145
+ return [...new Set(referencedProductions(definition).filter((reference) => reference !== name))];
146
+ }
147
+
148
+ function productionClosure(productions, roots, groups, coreProductions) {
149
+ const selectedRoots = new Set(roots);
150
+ const selectedGroups = new Set(groups);
151
+ const visited = new Set();
152
+ const pending = [...coreProductions, ...roots];
153
+ const allowedChildren = {};
154
+ const allowedGroups = {};
155
+
156
+ while (pending.length > 0) {
157
+ const name = pending.pop();
158
+ if (visited.has(name)) {
159
+ continue;
160
+ }
161
+ const definition = productions.get(name);
162
+ if (!definition) {
163
+ continue;
164
+ }
165
+ visited.add(name);
166
+
167
+ const references = referencedProductions(definition).filter((reference) => reference !== name);
168
+ if (isChildSelector(name)) {
169
+ allowedChildren[name] = selectorOptions(name, definition);
170
+ for (const reference of references) {
171
+ if (selectedRoots.has(reference)) {
172
+ pending.push(reference);
173
+ }
174
+ }
175
+ continue;
176
+ }
177
+ if (isGroupSelector(name)) {
178
+ allowedGroups[name] = selectorOptions(name, definition);
179
+ for (const reference of references) {
180
+ const groupSelected = [...selectedGroups].some((group) => reference === group || reference.endsWith(`-${group}`));
181
+ if (groupSelected) {
182
+ pending.push(reference);
183
+ }
184
+ }
185
+ continue;
186
+ }
187
+ pending.push(...references);
188
+ }
189
+
190
+ return {
191
+ names: [...visited].sort(),
192
+ allowedChildren,
193
+ allowedGroups
194
+ };
195
+ }
196
+
197
+ function normalizeComponents(components) {
198
+ return [...new Set(components.flatMap((value) => String(value).split(","))
199
+ .map((value) => value.trim())
200
+ .filter(Boolean))].sort();
201
+ }
202
+
203
+ function normalizeChildren(children, requestedComponents) {
204
+ const values = normalizeComponents(children || []);
205
+ for (const value of values) {
206
+ const [parent, child, ...extra] = value.split(".");
207
+ if (!parent || !child || extra.length > 0 || !requestedComponents.includes(parent)) {
208
+ throw new Error(`Invalid child selector '${value}'; expected <requested-component>.<child>`);
209
+ }
210
+ }
211
+ return values;
212
+ }
213
+
214
+ function normalizeInstances(values, requestedChildren) {
215
+ const bySelector = new Map();
216
+ for (const rawValue of values.flatMap((value) => Array.isArray(value) ? value : [value])) {
217
+ const text = String(rawValue).trim();
218
+ if (!text) continue;
219
+ const match = text.match(/^([A-Za-z][A-Za-z0-9]*)\.([A-Za-z][A-Za-z0-9]*)\[([^\]]+)\]\.([A-Za-z][A-Za-z0-9]*(?:\.[A-Za-z][A-Za-z0-9]*)?)=(.+)$/);
220
+ if (!match) {
221
+ throw new Error(`Invalid --instance condition '${text}'; expected parent.child[id].[group.]property=value`);
222
+ }
223
+ const [, parent, child, rawId, propertyPath, rawConditionValue] = match;
224
+ const family = `${parent}.${child}`;
225
+ if (!requestedChildren.includes(family)) {
226
+ throw new Error(`Instance condition '${text}' requires --children ${family}`);
227
+ }
228
+ const id = rawId.trim();
229
+ const value = rawConditionValue.trim();
230
+ if (!id || !value) throw new Error(`Invalid --instance condition '${text}'`);
231
+ const segments = propertyPath.split(".");
232
+ const condition = {
233
+ selector: `${family}[${id}].${propertyPath}`,
234
+ family,
235
+ parent,
236
+ child,
237
+ id,
238
+ group: segments.length === 2 ? segments[0] : null,
239
+ property: segments.at(-1),
240
+ value
241
+ };
242
+ const existing = bySelector.get(condition.selector);
243
+ if (existing && existing.value !== value) {
244
+ throw new Error(`Conflicting --instance conditions for ${condition.selector}`);
245
+ }
246
+ bySelector.set(condition.selector, condition);
247
+ }
248
+ return [...bySelector.values()].sort((left, right) => left.selector.localeCompare(right.selector));
249
+ }
250
+
251
+ function normalizeConditions(values, requestedComponents) {
252
+ const componentOrder = [...requestedComponents].sort((left, right) => right.length - left.length);
253
+ const bySelector = new Map();
254
+ for (const rawValue of values.flatMap((value) => Array.isArray(value) ? value : [value])) {
255
+ const text = String(rawValue).trim();
256
+ if (!text) {
257
+ continue;
258
+ }
259
+ const separator = text.indexOf("=");
260
+ if (separator <= 0 || separator === text.length - 1) {
261
+ throw new Error(`Invalid --when condition: ${text}; expected component[.group].property=value`);
262
+ }
263
+ const selector = text.slice(0, separator).trim();
264
+ const value = text.slice(separator + 1).trim();
265
+ const component = componentOrder.find((name) => selector === name || selector.startsWith(`${name}.`));
266
+ if (!component) {
267
+ throw new Error(`Condition selector must start with a requested component: ${selector}`);
268
+ }
269
+ const segments = selector.slice(component.length + 1).split(".").filter(Boolean);
270
+ if (segments.length < 1 || segments.length > 2) {
271
+ throw new Error(`Invalid condition selector: ${selector}; expected component[.group].property`);
272
+ }
273
+ const condition = {
274
+ selector,
275
+ component,
276
+ group: segments.length === 2 ? segments[0] : null,
277
+ property: segments.at(-1),
278
+ value
279
+ };
280
+ const existing = bySelector.get(selector);
281
+ if (existing && existing.value !== value) {
282
+ throw new Error(`Conflicting --when conditions for ${selector}: ${existing.value}, ${value}`);
283
+ }
284
+ bySelector.set(selector, condition);
285
+ }
286
+ return [...bySelector.values()].sort((left, right) => left.selector.localeCompare(right.selector));
287
+ }
288
+
289
+ function normalizedToken(value) {
290
+ return String(value).toLowerCase().replace(/[^a-z0-9]/g, "");
291
+ }
292
+
293
+ function compilerConditionState(condition, assumptions) {
294
+ if (!condition) return "active";
295
+ if (condition.conditions) {
296
+ const states = condition.conditions.map((child) => compilerConditionState(child, assumptions));
297
+ if ((condition.operator || "AND") === "AND") {
298
+ if (states.includes("inactive")) return "inactive";
299
+ return states.every((state) => state === "active") ? "active" : "conditional";
300
+ }
301
+ if (states.includes("active")) return "active";
302
+ return states.every((state) => state === "inactive") ? "inactive" : "conditional";
303
+ }
304
+ const actual = assumptions.get(String(condition.propertyId || ""))
305
+ ?? assumptions.get(condition.propertyName || "");
306
+ if (actual === undefined) return "conditional";
307
+ const expected = String(condition.value ?? "");
308
+ const values = (condition.values || []).map(String);
309
+ switch (condition.type) {
310
+ case "EQUALS": return actual === expected ? "active" : "inactive";
311
+ case "NOT_EQUALS": return actual !== expected ? "active" : "inactive";
312
+ case "IN_LIST": return values.includes(actual) ? "active" : "inactive";
313
+ case "NOT_IN_LIST": return values.includes(actual) ? "inactive" : "active";
314
+ case "NOT_NULL": return actual ? "active" : "inactive";
315
+ case "NULL": return actual ? "inactive" : "active";
316
+ case "STARTS_WITH": return actual.startsWith(expected) ? "active" : "inactive";
317
+ case "NOT_STARTS_WITH": return actual.startsWith(expected) ? "inactive" : "active";
318
+ default: return "conditional";
319
+ }
320
+ }
321
+
322
+ function compilerRecordProperties(record) {
323
+ return [
324
+ ...(record.properties || []),
325
+ ...Object.values(record.groups || {}).flat()
326
+ ];
327
+ }
328
+
329
+ function grammarComponentKeyword(definition) {
330
+ return definition?.match(/::=\s*"([A-Za-z][A-Za-z0-9]*)"/)?.[1] || null;
331
+ }
332
+
333
+ function grammarVariantProperties(productions, root) {
334
+ const names = [...productions.keys()].filter((name) => name === root || name.startsWith(`${root}-`));
335
+ return new Set(names.flatMap((name) =>
336
+ [...productions.get(name).matchAll(/"([A-Za-z][A-Za-z0-9]*)"\s+":"\s+<ws>/g)].map((match) => match[1])));
337
+ }
338
+
339
+ function grammarPropertyGroup(productions, component, property) {
340
+ const dslProperty = normalizeCompilerPropertyName(property);
341
+ const directName = `${component}-direct-property`;
342
+ if (productions.get(directName)?.includes(`"${dslProperty}"`)) return null;
343
+ const prefix = `${component}-`;
344
+ const suffix = "-property";
345
+ const matches = [...productions.entries()]
346
+ .filter(([name, definition]) => name.startsWith(prefix)
347
+ && name.endsWith(suffix)
348
+ && definition.includes(`"${dslProperty}"`))
349
+ .map(([name]) => name.slice(prefix.length, -suffix.length));
350
+ return matches.length === 1 ? matches[0] : null;
351
+ }
352
+
353
+ function matchGrammarVariant(productions, alternatives, record) {
354
+ const compilerProperties = new Set(compilerRecordProperties(record)
355
+ .map(({ propertyName }) => normalizeCompilerPropertyName(propertyName)));
356
+ const scored = alternatives.map((production) => {
357
+ const grammarProperties = grammarVariantProperties(productions, production);
358
+ const overlap = [...grammarProperties].filter((property) => compilerProperties.has(property)).length;
359
+ const union = new Set([...grammarProperties, ...compilerProperties]).size;
360
+ return { production, overlap, similarity: union ? overlap / union : 0 };
361
+ }).sort((left, right) => right.similarity - left.similarity || right.overlap - left.overlap);
362
+ if (!scored[0] || scored[0].overlap === 0) return null;
363
+ if (scored[1] && scored[0].similarity === scored[1].similarity && scored[0].overlap === scored[1].overlap) return null;
364
+ return scored[0];
365
+ }
366
+
367
+ function compilerChildDeclaration(productions, grammarProduction, child, hasVariants) {
368
+ const directProperties = productions.get(`${grammarProduction}-direct-property`) || "";
369
+ const usesNameAsIdentity = /"name"\s*":"[^\n]*\(\*\s*required\b/.test(directProperties);
370
+ if (usesNameAsIdentity) {
371
+ return {
372
+ declaration: `${child} <name> (`,
373
+ componentIdPolicy: "required-as-name"
374
+ };
375
+ }
376
+ return {
377
+ declaration: `${child} (`,
378
+ componentIdPolicy: hasVariants ? "omit-to-disambiguate-variant" : "optional"
379
+ };
380
+ }
381
+
382
+ function resolveCompilerChildren({ map, productions, requestedChildren, assumptionIndex }) {
383
+ const resolved = [];
384
+ const childrenByParent = new Map();
385
+ for (const selector of requestedChildren) {
386
+ const [parent, child] = selector.split(".");
387
+ if (!childrenByParent.has(parent)) childrenByParent.set(parent, new Set());
388
+ childrenByParent.get(parent).add(child);
389
+ }
390
+ for (const [parent, selectedChildren] of childrenByParent) {
391
+ const selector = productions.get(`${parent}-child-component`);
392
+ if (!selector) continue;
393
+ const alternatives = selectorOptions(`${parent}-child-component`, selector);
394
+ const byKeyword = new Map();
395
+ for (const production of alternatives) {
396
+ const keyword = grammarComponentKeyword(productions.get(production));
397
+ if (!keyword) continue;
398
+ if (!byKeyword.has(keyword)) byKeyword.set(keyword, []);
399
+ byKeyword.get(keyword).push(production);
400
+ }
401
+ for (const [child, grammarAlternatives] of byKeyword) {
402
+ if (!selectedChildren.has(child)) continue;
403
+ const candidates = map.componentTypes.filter((record) =>
404
+ record.singular === child && record.parentComponentType === parent);
405
+ if (candidates.length === 0) continue;
406
+ const classified = candidates.map((record) => ({
407
+ record,
408
+ state: compilerConditionState(record.parentDependsOn, assumptionIndex)
409
+ })).filter(({ state }) => state !== "inactive");
410
+ const active = classified.filter(({ state }) => state === "active");
411
+ const selectable = active.length > 0 ? active : classified;
412
+ if (selectable.length !== 1) continue;
413
+ const selected = selectable[0];
414
+ const grammar = matchGrammarVariant(productions, grammarAlternatives, selected.record);
415
+ if (!grammar) continue;
416
+ const declaration = compilerChildDeclaration(
417
+ productions,
418
+ grammar.production,
419
+ child,
420
+ grammarAlternatives.length > 1
421
+ );
422
+ resolved.push({
423
+ parent,
424
+ child,
425
+ compilerState: selected.state,
426
+ grammarProduction: grammar.production,
427
+ grammarSimilarity: Number(grammar.similarity.toFixed(4)),
428
+ ...declaration,
429
+ record: selected.record
430
+ });
431
+ }
432
+ }
433
+ return resolved;
434
+ }
435
+
436
+ function buildInstanceContracts(resolvedChildren, requestedInstances, requestedGroups, baseAssumptionIndex) {
437
+ const families = new Map(resolvedChildren.map((child) => [`${child.parent}.${child.child}`, child]));
438
+ const grouped = new Map();
439
+ for (const condition of requestedInstances) {
440
+ const key = `${condition.family}[${condition.id}]`;
441
+ if (!grouped.has(key)) grouped.set(key, []);
442
+ grouped.get(key).push(condition);
443
+ }
444
+ const contracts = [];
445
+ for (const [selector, conditions] of grouped) {
446
+ const resolved = families.get(conditions[0].family);
447
+ if (!resolved) {
448
+ throw new Error(`Instance '${selector}' has no unambiguous resolved child record`);
449
+ }
450
+ const assumptionIndex = new Map([...baseAssumptionIndex]
451
+ .filter(([key]) => /^\d+$/.test(String(key))));
452
+ const assumptions = [];
453
+ const assumedGroups = new Set();
454
+ for (const condition of conditions) {
455
+ const found = findCompilerProperty(resolved.record, condition);
456
+ if (!found) {
457
+ throw new Error(`Instance property '${condition.selector}' is not present in compiler metadata`);
458
+ }
459
+ const value = compilerValue(found.property, condition.value);
460
+ const path = [selector, found.group, normalizeCompilerPropertyName(found.property.propertyName)]
461
+ .filter(Boolean)
462
+ .join(".");
463
+ assumptions.push({
464
+ path,
465
+ dslValue: condition.value,
466
+ compilerPropertyId: found.property.propertyId || null,
467
+ compilerValue: value
468
+ });
469
+ if (found.group) assumedGroups.add(found.group);
470
+ assumptionIndex.set(found.property.propertyName, value);
471
+ if (found.property.propertyId) assumptionIndex.set(String(found.property.propertyId), value);
472
+ }
473
+ const groupedCandidates = Object.entries(resolved.record.groups || {})
474
+ .filter(([group]) => requestedGroups.includes(group))
475
+ .flatMap(([group, items]) => items.map((property) => ({ group, property })));
476
+ const groupedIds = new Set(groupedCandidates.map(({ property }) => String(property.propertyId || "")));
477
+ const candidates = [
478
+ ...groupedCandidates,
479
+ ...(resolved.record.properties || [])
480
+ .filter((property) => !groupedIds.has(String(property.propertyId || "")))
481
+ .map((property) => ({ group: null, property }))
482
+ ];
483
+ const propertyStates = candidates.map(({ group, property }) => ({
484
+ group,
485
+ property,
486
+ state: property.dependsOn
487
+ ? compilerConditionState(property.dependsOn, assumptionIndex)
488
+ : "active"
489
+ }));
490
+ const requiredActiveProperties = propertyStates
491
+ .filter(({ property, state }) => property.required && state === "active")
492
+ .map(({ group, property }) => ({
493
+ path: [selector, group, normalizeCompilerPropertyName(property.propertyName)].filter(Boolean).join("."),
494
+ compilerPropertyId: property.propertyId || null
495
+ }))
496
+ .sort((left, right) => left.path.localeCompare(right.path));
497
+ const omitProperties = [...new Set(propertyStates
498
+ .filter(({ group, property, state }) => group && assumedGroups.has(group)
499
+ && property.required && state === "inactive")
500
+ .map(({ group, property }) =>
501
+ [selector, group, normalizeCompilerPropertyName(property.propertyName)].filter(Boolean).join(".")))]
502
+ .sort((left, right) => left.localeCompare(right));
503
+ const repositoryPolicies = INSTANCE_POLICY_RULES
504
+ .filter((rule) => rule.family === conditions[0].family)
505
+ .map((rule) => {
506
+ const bindings = rule.bindings.flatMap((binding) => {
507
+ const assumption = assumptions.find(({ path }) => path.endsWith(binding.whenAssumptionEndsWith));
508
+ if (!assumption) return [];
509
+ return [{
510
+ source: assumption.path,
511
+ target: `${selector}.${binding.target}`,
512
+ value: binding.transform === "reference" ? `@${assumption.dslValue}` : assumption.dslValue
513
+ }];
514
+ });
515
+ return {
516
+ ruleId: rule.id,
517
+ declarationId: rule.declarationIdPrefix
518
+ ? `${rule.declarationIdPrefix}${conditions[0].id}`.toUpperCase()
519
+ : null,
520
+ exactDeclaration: rule.declarationIdPrefix
521
+ ? `${resolved.child} ${`${rule.declarationIdPrefix}${conditions[0].id}`.toUpperCase()} (`
522
+ : resolved.declaration,
523
+ requiredBlocks: rule.requiredBlocks,
524
+ forbidBlocks: rule.forbidBlocks || [],
525
+ requiredProperties: rule.requiredProperties.map((property) => `${selector}.${property}`),
526
+ bindings
527
+ };
528
+ });
529
+ contracts.push({
530
+ selector,
531
+ family: conditions[0].family,
532
+ id: conditions[0].id,
533
+ grammarProduction: resolved.grammarProduction,
534
+ declaration: resolved.declaration,
535
+ componentIdPolicy: resolved.componentIdPolicy,
536
+ assumptions,
537
+ requiredActiveProperties,
538
+ omitProperties,
539
+ repositoryPolicies
540
+ });
541
+ }
542
+ return contracts.sort((left, right) => left.selector.localeCompare(right.selector));
543
+ }
544
+
545
+ function findCompilerProperty(record, condition) {
546
+ const candidates = [
547
+ ...(record.properties || []).map((property) => ({ group: null, property })),
548
+ ...Object.entries(record.groups || {}).flatMap(([group, properties]) =>
549
+ properties.map((property) => ({ group, property })))
550
+ ].filter(({ property }) => normalizeCompilerPropertyName(property.propertyName) === condition.property);
551
+ if (condition.group) {
552
+ return candidates.find(({ group }) => group === condition.group) || null;
553
+ }
554
+ return candidates.find(({ group }) => group === null) || candidates[0] || null;
555
+ }
556
+
557
+ function compilerValue(property, dslValue) {
558
+ const normalizedDslValue = normalizeCompilerDslValue(property, dslValue);
559
+ const values = property.lov?.values || [];
560
+ const match = values.find(({ name, returnValue }) =>
561
+ normalizedToken(name) === normalizedToken(normalizedDslValue)
562
+ || normalizedToken(returnValue) === normalizedToken(normalizedDslValue));
563
+ return match?.returnValue || COMPILER_VALUE_FALLBACKS.get(normalizedDslValue) || normalizedDslValue;
564
+ }
565
+
566
+ function grammarRequiredPropertyNames(definition = "") {
567
+ return [...String(definition).split(/\r?\n/).map((line) => {
568
+ const match = line.match(/"([A-Za-z][A-Za-z0-9]*)"\s*":"\s*<ws>/);
569
+ return match && /\(\*\s*required\b/.test(line) ? match[1] : "";
570
+ }).filter(Boolean)];
571
+ }
572
+
573
+ function grammarRequiredChildPaths(productions, grammarProduction, selector) {
574
+ const paths = new Set(grammarRequiredPropertyNames(productions.get(`${grammarProduction}-direct-property`) || "")
575
+ .map((property) => `${selector}.${property}`));
576
+ const groupDefinition = productions.get(`${grammarProduction}-group-block`) || "";
577
+ for (const reference of groupDefinition.matchAll(/<([^>]+)>/g)) {
578
+ const productionName = reference[1];
579
+ const blockDefinition = productions.get(productionName) || "";
580
+ const blockName = blockDefinition.match(/::=.*?"([A-Za-z][A-Za-z0-9]*)"\s*<ws>\s*"\{"/)?.[1];
581
+ if (!blockName) continue;
582
+ for (const property of grammarRequiredPropertyNames(productions.get(`${productionName}-property`) || "")) {
583
+ paths.add(`${selector}.${blockName}.${property}`);
584
+ }
585
+ }
586
+ return paths;
587
+ }
588
+
589
+ function grammarCompilerConformance(productions, compilerContract) {
590
+ if (compilerContract?.status !== "resolved") return [];
591
+ return (compilerContract.resolvedChildren || [])
592
+ .filter((child) => `${child.parent}.${child.child}` === "region.column")
593
+ .map((child) => {
594
+ const selector = `${child.parent}.${child.child}`;
595
+ const grammarRequired = grammarRequiredChildPaths(productions, child.grammarProduction, selector);
596
+ const compilerProperties = new Map((compilerContract.requiredActiveProperties || [])
597
+ .filter(({ path: propertyPath }) => propertyPath.startsWith(`${selector}.`))
598
+ .map((entry) => [entry.path, entry]));
599
+ // A compiler default does not make a property optional. The metadata keeps
600
+ // these facts separate: `isRequired` controls structural presence while
601
+ // `defaultValue` supplies the value used by the builder UI/compiler.
602
+ // Treating every defaulted property as optional caused a false mismatch for
603
+ // Media List column source.type (required, default DB_COLUMN).
604
+ const compilerRequired = new Set([...compilerProperties]
605
+ .filter(([, entry]) => entry.compilerRequired !== false)
606
+ .map(([propertyPath]) => propertyPath));
607
+ const compilerDefaulted = new Set([...compilerProperties]
608
+ .filter(([, entry]) => entry.defaulted)
609
+ .map(([propertyPath]) => propertyPath));
610
+ const sourceTypePath = `${selector}.source.type`;
611
+ // This probe owns the source.type contract. Other column groups are only
612
+ // comparable after their compiler metadata has been projected into the
613
+ // matching grammar group; treating an unselected group as a direct
614
+ // property would create unrelated false mismatches.
615
+ const mismatches = [sourceTypePath]
616
+ .filter((propertyPath) => grammarRequired.has(propertyPath) !== compilerRequired.has(propertyPath))
617
+ .map((propertyPath) => ({
618
+ path: propertyPath,
619
+ grammarRequired: grammarRequired.has(propertyPath),
620
+ compilerRequired: compilerRequired.has(propertyPath),
621
+ compilerDefaulted: compilerDefaulted.has(propertyPath)
622
+ }));
623
+ const sourceType = {
624
+ path: sourceTypePath,
625
+ grammarRequired: grammarRequired.has(sourceTypePath),
626
+ compilerRequired: compilerRequired.has(sourceTypePath),
627
+ compilerDefaulted: compilerDefaulted.has(sourceTypePath),
628
+ compilerDefaultValue: compilerProperties.get(sourceTypePath)?.defaultValue ?? null,
629
+ emission: compilerRequired.has(sourceTypePath) ? "required" : compilerDefaulted.has(sourceTypePath) ? "omit" : "unresolved"
630
+ };
631
+ return {
632
+ selector,
633
+ grammarProduction: child.grammarProduction,
634
+ status: mismatches.length ? "mismatch" : "matched",
635
+ blocking: mismatches.length > 0,
636
+ mismatches,
637
+ sourceType
638
+ };
639
+ });
640
+ }
641
+
642
+ export function probeMediaListGrammarConformance({ contract } = {}) {
643
+ return (contract?.compilerContract?.grammarConformance || [])
644
+ .filter(({ selector }) => selector === "region.column");
645
+ }
646
+
647
+ function dslValue(property, value) {
648
+ if (value === "Y") return true;
649
+ if (value === "N") return false;
650
+ const fallback = [...COMPILER_VALUE_FALLBACKS].find(([, compilerToken]) => compilerToken === value);
651
+ if (fallback) return fallback[0];
652
+ const match = (property?.lov?.values || []).find((item) => String(item.returnValue) === String(value));
653
+ if (match?.name) return match.name;
654
+ return value;
655
+ }
656
+
657
+ function conditionLeaves(condition) {
658
+ if (!condition) return [];
659
+ if (condition.conditions) return condition.conditions.flatMap(conditionLeaves);
660
+ return [condition];
661
+ }
662
+
663
+ function activationCondition(condition, siblingProperties) {
664
+ if (condition.conditions) {
665
+ const conditions = condition.conditions.map((child) => activationCondition(child, siblingProperties));
666
+ if (conditions.some((item) => !item)) return null;
667
+ return { operator: String(condition.operator || "AND").toLowerCase(), conditions };
668
+ }
669
+ const dependency = siblingProperties.find((property) =>
670
+ String(property.propertyId || "") === String(condition.propertyId || "")
671
+ || normalizeCompilerPropertyName(property.propertyName) === normalizeCompilerPropertyName(condition.propertyName));
672
+ if (!dependency) return null;
673
+ const operator = {
674
+ EQUALS: "equals",
675
+ NOT_EQUALS: "notEquals",
676
+ IN_LIST: "in",
677
+ NOT_IN_LIST: "notIn",
678
+ NULL: "isNull",
679
+ NOT_NULL: "isNotNull"
680
+ }[condition.type];
681
+ if (!operator) return null;
682
+ const result = {
683
+ property: normalizeCompilerPropertyName(dependency.propertyName),
684
+ operator,
685
+ missingAllowed: condition.hasToExist === false
686
+ };
687
+ if (condition.value !== undefined) result.value = dslValue(dependency, condition.value);
688
+ if (condition.values) result.values = condition.values.map((value) => dslValue(dependency, value));
689
+ return result;
690
+ }
691
+
692
+ async function buildCompilerContract(
693
+ layout,
694
+ productions,
695
+ requestedComponents,
696
+ requestedChildren,
697
+ requestedInstances,
698
+ requestedGroups,
699
+ selectedConditions,
700
+ compilerOracleHome
701
+ ) {
702
+ const toolRoot = layout.packaged
703
+ ? path.join(layout.root, "tools")
704
+ : path.join(layout.root, "references/policies", "apexlang", "compiler-prop-map");
705
+ try {
706
+ const [{ buildOracleNormalizedMap, readOracleRuntimeMetadata, ORACLE_NORMALIZER_VERSION }, { resolveOracleRuntime }] = await Promise.all([
707
+ import(pathToFileURL(path.join(toolRoot, "query-valid-props-normalize.mjs")).href),
708
+ import(pathToFileURL(path.join(toolRoot, "query-valid-props-runtime.mjs")).href)
709
+ ]);
710
+ const runtime = resolveOracleRuntime(compilerOracleHome);
711
+ const { metadata, buildId, metadataHash } = readOracleRuntimeMetadata(runtime.compilerJarPath);
712
+ const map = buildOracleNormalizedMap({
713
+ metadata,
714
+ compilerJarPath: runtime.compilerJarPath,
715
+ oracleHome: runtime.oracleHome,
716
+ source: runtime.source
717
+ });
718
+ const records = new Map(requestedComponents.map((component) => [
719
+ component,
720
+ map.componentTypes.find((record) => record.singular === component && !record.parentComponentType)
721
+ || map.componentTypes.find((record) => record.singular === component)
722
+ ]));
723
+ const assumptions = [];
724
+ const assumptionIndex = new Map();
725
+ for (const condition of selectedConditions) {
726
+ const record = records.get(condition.component);
727
+ const found = record ? findCompilerProperty(record, condition) : null;
728
+ if (!found) continue;
729
+ const value = compilerValue(found.property, condition.value);
730
+ const grammarGroup = condition.group
731
+ || grammarPropertyGroup(productions, condition.component, found.property.propertyName);
732
+ assumptions.push({
733
+ selector: condition.selector,
734
+ path: [condition.component, grammarGroup, normalizeCompilerPropertyName(found.property.propertyName)]
735
+ .filter(Boolean)
736
+ .join("."),
737
+ dslValue: condition.value,
738
+ compilerPropertyId: found.property.propertyId || null,
739
+ compilerValue: value
740
+ });
741
+ assumptionIndex.set(found.property.propertyName, value);
742
+ if (found.property.propertyId) assumptionIndex.set(String(found.property.propertyId), value);
743
+ }
744
+ const resolvedChildren = resolveCompilerChildren({
745
+ map,
746
+ productions,
747
+ requestedChildren,
748
+ assumptionIndex
749
+ });
750
+ const instanceContracts = buildInstanceContracts(
751
+ resolvedChildren,
752
+ requestedInstances,
753
+ requestedGroups,
754
+ assumptionIndex
755
+ );
756
+ for (const child of resolvedChildren) {
757
+ records.set(`${child.parent}.${child.child}`, child.record);
758
+ }
759
+ const properties = [];
760
+ const activationRules = [];
761
+ for (const [component, record] of records) {
762
+ if (!record) continue;
763
+ const groupedCandidates = Object.entries(record.groups || {})
764
+ .filter(([group]) => requestedGroups.includes(group))
765
+ .flatMap(([group, items]) => items.map((property) => ({ group, property })));
766
+ for (const [group, items] of Object.entries(record.groups || {})) {
767
+ if (!requestedGroups.includes(group)) continue;
768
+ for (const property of items) {
769
+ if (!property.required || !property.dependsOn) continue;
770
+ if (compilerConditionState(property.dependsOn, assumptionIndex) !== "conditional") continue;
771
+ const leaves = conditionLeaves(property.dependsOn);
772
+ if (!leaves.some((leaf) => leaf.hasToExist === false)) continue;
773
+ const siblingOnly = leaves.length > 0 && leaves.every((leaf) => items.some((candidate) =>
774
+ String(candidate.propertyId || "") === String(leaf.propertyId || "")
775
+ || candidate.propertyName === leaf.propertyName));
776
+ if (!siblingOnly) continue;
777
+ const activeWhen = activationCondition(property.dependsOn, items);
778
+ if (!activeWhen) continue;
779
+ activationRules.push({
780
+ path: `${component}.${group}.${normalizeCompilerPropertyName(property.propertyName)}`,
781
+ activeWhen,
782
+ otherwise: "omit"
783
+ });
784
+ }
785
+ }
786
+ const groupedIds = new Set(groupedCandidates.map(({ property }) => String(property.propertyId || "")));
787
+ const candidates = [
788
+ ...groupedCandidates,
789
+ ...(record.properties || [])
790
+ .filter((property) => !groupedIds.has(String(property.propertyId || "")))
791
+ .map((property) => ({ group: null, property }))
792
+ ];
793
+ for (const { group, property } of candidates) {
794
+ if (!property.required) continue;
795
+ const state = property.dependsOn
796
+ ? compilerConditionState(property.dependsOn, assumptionIndex)
797
+ : "active";
798
+ if (state !== "active") continue;
799
+ const propertyPath = [component, group, normalizeCompilerPropertyName(property.propertyName)]
800
+ .filter(Boolean)
801
+ .join(".");
802
+ properties.push({
803
+ path: propertyPath,
804
+ compilerPropertyId: property.propertyId || null,
805
+ ...(component === "region.column"
806
+ && property.defaultValue !== null
807
+ && property.defaultValue !== undefined
808
+ ? { defaulted: true, defaultValue: dslValue(property, property.defaultValue) }
809
+ : {})
810
+ });
811
+ }
812
+ }
813
+ const uniqueProperties = [...new Map(properties.map((item) => [item.path, item])).values()]
814
+ .sort((left, right) => left.path.localeCompare(right.path));
815
+ return {
816
+ status: "resolved",
817
+ source: "compiler-runtime-metadata",
818
+ buildID: buildId,
819
+ metadataHash,
820
+ normalizerVersion: ORACLE_NORMALIZER_VERSION,
821
+ assumptions,
822
+ requiredActiveProperties: uniqueProperties,
823
+ resolvedChildren: resolvedChildren.map(({ record: _record, ...child }) => child),
824
+ instances: instanceContracts,
825
+ activationRules: [...new Map(activationRules.map((item) => [`${item.path}:${JSON.stringify(item.activeWhen)}`, item])).values()]
826
+ .sort((left, right) => left.path.localeCompare(right.path)
827
+ || JSON.stringify(left.activeWhen).localeCompare(JSON.stringify(right.activeWhen)))
828
+ };
829
+ } catch (error) {
830
+ return {
831
+ status: "unavailable",
832
+ reason: error instanceof Error ? error.message : String(error),
833
+ assumptions: [],
834
+ requiredActiveProperties: [],
835
+ resolvedChildren: [],
836
+ instances: [],
837
+ activationRules: []
838
+ };
839
+ }
840
+ }
841
+
842
+ function productionContext(name, requestedComponents) {
843
+ const component = [...requestedComponents]
844
+ .sort((left, right) => right.length - left.length)
845
+ .find((candidate) => name === candidate || name.startsWith(`${candidate}-`));
846
+ if (!component) {
847
+ return null;
848
+ }
849
+ const suffix = name.slice(component.length + 1);
850
+ const group = suffix.endsWith("-property") ? suffix.slice(0, -"-property".length) : null;
851
+ return { component, group };
852
+ }
853
+
854
+ function narrowDefinition(name, definition, conditions, requestedComponents) {
855
+ const context = productionContext(name, requestedComponents);
856
+ if (!context || !name.endsWith("-property")) {
857
+ return { definition, narrowedEnums: 0 };
858
+ }
859
+ let narrowedEnums = 0;
860
+ const nextDefinition = definition.split("\n").map((line) => {
861
+ const propertyMatch = line.match(/"([^"]+)"\s*":"\s*<ws>\s*\(([^)]+)\)/);
862
+ if (!propertyMatch) {
863
+ return line;
864
+ }
865
+ const property = propertyMatch[1];
866
+ const condition = conditions.find((item) => item.component === context.component
867
+ && item.property === property
868
+ && (!item.group || item.group === context.group || context.group === "direct"));
869
+ if (!condition) {
870
+ return line;
871
+ }
872
+ const acceptedValues = [...propertyMatch[2].matchAll(/"([^"]+)"/g)].map((match) => match[1]);
873
+ const selected = acceptedValues.find((value) => normalizedToken(value) === normalizedToken(condition.value));
874
+ if (!selected) {
875
+ return line;
876
+ }
877
+ narrowedEnums += 1;
878
+ return line.replace(`(${propertyMatch[2]})`, `"${selected}"`);
879
+ }).join("\n");
880
+ return { definition: nextDefinition, narrowedEnums };
881
+ }
882
+
883
+ function selectSemanticRules(catalog, requestedComponents, selectedConditions) {
884
+ const componentSet = new Set(requestedComponents);
885
+ const conditionSet = new Set(selectedConditions.map(({ selector, value }) => `${selector}=${value}`));
886
+ return catalog.rules.filter(({ match = {} }) =>
887
+ (match.components || []).every((component) => componentSet.has(component))
888
+ && (match.conditions || []).every((condition) => conditionSet.has(condition))
889
+ ).map(({ match: _match, ...rule }) => rule);
890
+ }
891
+
892
+ function generationChecklist(rules) {
893
+ return rules.map(({ id, level, scope, check = null }) => ({ ruleId: id, level, scope, check }));
894
+ }
895
+
896
+ function componentBlocks(text, component) {
897
+ const pattern = new RegExp(`^([ \\t]*)${component}\\b[^\\n]*\\(\\s*$([\\s\\S]*?)^\\1\\)\\s*$`, "gm");
898
+ return [...text.matchAll(pattern)].map((match) => match[0]);
899
+ }
900
+
901
+ function cleanScalar(value) {
902
+ return String(value).trim().replace(/^"|"$/g, "");
903
+ }
904
+
905
+ function checkCanonicalPageIdentity(artifactPath, text) {
906
+ const filenameMatch = path.basename(artifactPath).match(/^p(\d{5})-([a-z0-9-]+)\.apx$/i);
907
+ if (!filenameMatch) {
908
+ return { passed: false, message: "Page artifact filename must use pNNNNN-<slug>.apx." };
909
+ }
910
+ const expectedPage = Number.parseInt(filenameMatch[1], 10);
911
+ const expectedAlias = filenameMatch[2].toUpperCase();
912
+ const declarationMatch = text.match(/^page\s+(\d+)\s*\(/m);
913
+ const pagePropertyMatch = text.match(/^\s+page:\s*(\d+)\s*$/m);
914
+ const aliasMatch = text.match(/^\s+alias:\s*(.+?)\s*$/m);
915
+ const actualAlias = aliasMatch ? cleanScalar(aliasMatch[1]) : "";
916
+ const passed = Number.parseInt(declarationMatch?.[1] || "", 10) === expectedPage
917
+ && Number.parseInt(pagePropertyMatch?.[1] || "", 10) === expectedPage
918
+ && actualAlias === expectedAlias;
919
+ return {
920
+ passed,
921
+ message: passed
922
+ ? `Page declaration, page property, and alias match ${path.basename(artifactPath)}.`
923
+ : `Expected page ${expectedPage} and alias ${expectedAlias} from ${path.basename(artifactPath)}.`
924
+ };
925
+ }
926
+
927
+ function checkNormalStaticContentSlot(_artifactPath, text) {
928
+ const staticRegions = componentBlocks(text, "region").filter((block) => /^\s*type:\s*staticContent\s*$/m.test(block));
929
+ const passed = staticRegions.length > 0 && staticRegions.every((block) => /^\s*slot:\s*body\s*$/m.test(block));
930
+ return {
931
+ passed,
932
+ message: passed
933
+ ? "Every selected static-content region uses layout.slot: body."
934
+ : "A normal-page static-content region is missing layout.slot: body."
935
+ };
936
+ }
937
+
938
+ function checkNormalPageBreadcrumbRegion(_artifactPath, text) {
939
+ const breadcrumbRegions = componentBlocks(text, "region").filter((block) => /^\s*type:\s*breadcrumb\s*$/m.test(block));
940
+ const passed = breadcrumbRegions.some((block) => /^\s*breadcrumb:\s*@breadcrumb\s*$/m.test(block)
941
+ && /^\s*slot:\s*breadcrumbBar\s*$/m.test(block));
942
+ return {
943
+ passed,
944
+ message: passed
945
+ ? "The page renders the required breadcrumb region."
946
+ : "Add a breadcrumb region with source.breadcrumb: @breadcrumb and layout.slot: breadcrumbBar."
947
+ };
948
+ }
949
+
950
+ function checkNormalPageBodyRegions(_artifactPath, text) {
951
+ const bodyRegions = componentBlocks(text, "region")
952
+ .filter((block) => !/^\s*type:\s*breadcrumb\s*$/m.test(block));
953
+ const passed = bodyRegions.length === 0 || bodyRegions.every((block) => /^\s*slot:\s*body\s*$/m.test(block));
954
+ return {
955
+ passed,
956
+ message: passed
957
+ ? "Every non-breadcrumb region on the normal page uses layout.slot: body."
958
+ : "A non-breadcrumb region on the normal page must use layout.slot: body."
959
+ };
960
+ }
961
+
962
+ function checkSameAppButtonTarget(_artifactPath, text) {
963
+ const redirectButtons = componentBlocks(text, "button").filter((block) => /^\s*action:\s*redirectThisApp\s*$/m.test(block));
964
+ const passed = redirectButtons.length > 0 && redirectButtons.every((block) => /^\s*target:\s*\{\s*$/m.test(block));
965
+ return {
966
+ passed,
967
+ message: passed
968
+ ? "Every redirectThisApp button uses an object-valued target."
969
+ : "A redirectThisApp button is missing an object-valued target: { ... } property."
970
+ };
971
+ }
972
+
973
+ const CONTRACT_CHECKS = {
974
+ "canonical-page-identity": checkCanonicalPageIdentity,
975
+ "normal-static-content-slot": checkNormalStaticContentSlot,
976
+ "normal-page-breadcrumb-region": checkNormalPageBreadcrumbRegion,
977
+ "normal-page-body-regions": checkNormalPageBodyRegions,
978
+ "same-app-button-target": checkSameAppButtonTarget
979
+ };
980
+
981
+ function cacheFileName(grammarSha256, semanticRulesSha256, compilerIdentity, components, children, instances, groups, conditions) {
982
+ const selectionHash = createHash("sha256")
983
+ .update(`${semanticRulesSha256}|${compilerIdentity}|${components.join(",")}|${children.join(",")}|${instances.map(({ selector, value }) => `${selector}=${value}`).join(",")}|${groups.join(",")}|${conditions.map(({ selector, value }) => `${selector}=${value}`).join(",")}`)
984
+ .digest("hex")
985
+ .slice(0, 16);
986
+ return `${grammarSha256}-${selectionHash}.json`;
987
+ }
988
+
989
+ /**
990
+ * Return a compact EBNF closure for explicitly selected component productions.
991
+ * Child selector rules are retained as summaries but recursively expanded only
992
+ * for child components included in the explicit selection.
993
+ */
994
+ export async function buildGrammarContract({
995
+ components,
996
+ children = [],
997
+ instances = [],
998
+ groups = [],
999
+ conditions = [],
1000
+ cacheDir = "",
1001
+ useCache = true,
1002
+ compilerOracleHome = ""
1003
+ } = {}) {
1004
+ const requestedComponents = normalizeComponents(components || []);
1005
+ const requestedGroups = normalizeComponents(groups || []);
1006
+ const requestedChildren = normalizeChildren(children || [], requestedComponents);
1007
+ const requestedInstances = normalizeInstances(instances || [], requestedChildren);
1008
+ if (requestedComponents.length === 0) {
1009
+ throw new Error("At least one component is required");
1010
+ }
1011
+
1012
+ const layout = resolveLayout();
1013
+ const sourcePath = layout.grammarPath;
1014
+ const grammarText = await fs.readFile(sourcePath, "utf8");
1015
+ const grammarSha256 = createHash("sha256").update(grammarText).digest("hex");
1016
+ const semanticRulesText = await fs.readFile(layout.semanticRulesPath, "utf8");
1017
+ const semanticRulesSha256 = createHash("sha256").update(semanticRulesText).digest("hex");
1018
+ const semanticRulesCatalog = JSON.parse(semanticRulesText);
1019
+ const productions = parseProductions(grammarText);
1020
+ const unknownComponents = requestedComponents.filter((name) => !productions.has(name));
1021
+ if (unknownComponents.length > 0) {
1022
+ throw new Error(`Unknown grammar component production(s): ${unknownComponents.join(", ")}`);
1023
+ }
1024
+ const selectedConditions = normalizeConditions(conditions || [], requestedComponents);
1025
+ const compilerContract = await buildCompilerContract(
1026
+ layout,
1027
+ productions,
1028
+ requestedComponents,
1029
+ requestedChildren,
1030
+ requestedInstances,
1031
+ requestedGroups,
1032
+ selectedConditions,
1033
+ compilerOracleHome
1034
+ );
1035
+ compilerContract.grammarConformance = grammarCompilerConformance(productions, compilerContract);
1036
+ const compilerIdentity = compilerContract.status === "resolved"
1037
+ ? `${compilerContract.buildID}:${compilerContract.metadataHash}`
1038
+ : `unavailable:${compilerContract.reason}`;
1039
+
1040
+ const resolvedCacheDir = cacheDir
1041
+ ? path.resolve(cacheDir)
1042
+ : layout.packaged
1043
+ ? path.join(path.resolve(process.env.APEXLANG_OUTPUT_ROOT || process.cwd()), ".apexlang-cache", "grammar-contracts")
1044
+ : path.join(layout.root, "artifacts", "apexlang", "cache", "grammar-contracts");
1045
+ const cachePath = path.join(resolvedCacheDir, cacheFileName(grammarSha256, semanticRulesSha256, compilerIdentity, requestedComponents, requestedChildren, requestedInstances, requestedGroups, selectedConditions));
1046
+ if (useCache) {
1047
+ try {
1048
+ const cached = JSON.parse(await fs.readFile(cachePath, "utf8"));
1049
+ return { contract: cached, cachePath, cacheStatus: "hit" };
1050
+ } catch (error) {
1051
+ if (error?.code !== "ENOENT") {
1052
+ throw error;
1053
+ }
1054
+ }
1055
+ }
1056
+
1057
+ const resolvedGrammarChildren = compilerContract.resolvedChildren.map(({ grammarProduction }) => grammarProduction);
1058
+ const closure = productionClosure(
1059
+ productions,
1060
+ [...requestedComponents, ...resolvedGrammarChildren],
1061
+ requestedGroups,
1062
+ DEFAULT_CORE_PRODUCTIONS
1063
+ );
1064
+ let narrowedEnumCount = 0;
1065
+ const contractProductions = closure.names.map((name) => {
1066
+ const narrowed = narrowDefinition(name, productions.get(name), selectedConditions, requestedComponents);
1067
+ narrowedEnumCount += narrowed.narrowedEnums;
1068
+ return { name, definition: narrowed.definition };
1069
+ });
1070
+ const semanticRules = selectSemanticRules(semanticRulesCatalog, requestedComponents, selectedConditions);
1071
+ const checklist = generationChecklist(semanticRules);
1072
+ const contract = {
1073
+ schemaVersion: CONTRACT_SCHEMA_VERSION,
1074
+ purpose: "Task-scoped APEXlang syntax contract; compiler truth remains authoritative for semantic legality.",
1075
+ grammar: {
1076
+ sha256: grammarSha256,
1077
+ source: layout.packaged
1078
+ ? "assets/grammar/apexlang.ebnf"
1079
+ : "assets/grammar/apexlang.ebnf"
1080
+ },
1081
+ requestedComponents,
1082
+ requestedChildren,
1083
+ requestedInstances,
1084
+ requestedGroups,
1085
+ selectedConditions,
1086
+ semanticContract: {
1087
+ source: layout.packaged
1088
+ ? "assets/contracts/grammar-semantic-rules.json"
1089
+ : "assets/contracts/grammar-semantic-rules.json",
1090
+ sha256: semanticRulesSha256,
1091
+ rules: semanticRules,
1092
+ generationChecklist: checklist
1093
+ },
1094
+ compilerContract,
1095
+ expansionPolicy: {
1096
+ mode: "compiler-resolved-child-closure",
1097
+ description: "Child selectors remain summaries; compiler-compatible child variants are resolved from parent assumptions and expanded only when one structural match is unambiguous."
1098
+ },
1099
+ allowedChildren: closure.allowedChildren,
1100
+ allowedGroups: closure.allowedGroups,
1101
+ productions: contractProductions,
1102
+ stats: {
1103
+ productionCount: contractProductions.length,
1104
+ definitionCharacters: contractProductions.reduce((total, item) => total + item.definition.length, 0),
1105
+ narrowedEnumCount,
1106
+ semanticRuleCount: semanticRules.length,
1107
+ checklistCount: checklist.length,
1108
+ semanticContractCharacters: JSON.stringify({ rules: semanticRules, generationChecklist: checklist }).length,
1109
+ compilerContractCharacters: JSON.stringify(compilerContract).length,
1110
+ fullGrammarCharacters: grammarText.length
1111
+ }
1112
+ };
1113
+
1114
+ if (useCache) {
1115
+ await ensureDir(resolvedCacheDir);
1116
+ await fs.writeFile(cachePath, `${JSON.stringify(contract, null, 2)}\n`, "utf8");
1117
+ }
1118
+ return { contract, cachePath, cacheStatus: useCache ? "miss" : "disabled" };
1119
+ }
1120
+
1121
+ /** Audit deterministic MUST checks for one generated artifact. */
1122
+ export async function auditGrammarContract({ contract, artifactPath } = {}) {
1123
+ if (!contract?.semanticContract?.generationChecklist) {
1124
+ throw new Error("A grammar contract with semanticContract.generationChecklist is required");
1125
+ }
1126
+ if (!artifactPath) {
1127
+ throw new Error("An artifact path is required");
1128
+ }
1129
+ const resolvedArtifactPath = path.resolve(artifactPath);
1130
+ const text = await fs.readFile(resolvedArtifactPath, "utf8");
1131
+ const checks = [];
1132
+ const outOfScope = [];
1133
+ const decisions = [];
1134
+ for (const item of contract.semanticContract.generationChecklist) {
1135
+ if (item.level === "decision") {
1136
+ decisions.push({ ...item, status: "intent-dependent" });
1137
+ continue;
1138
+ }
1139
+ if (item.scope !== "page-local") {
1140
+ outOfScope.push({ ...item, status: "out-of-artifact-scope" });
1141
+ continue;
1142
+ }
1143
+ const check = CONTRACT_CHECKS[item.check];
1144
+ if (!check) {
1145
+ checks.push({ ...item, status: "unverified", message: `No deterministic check is registered for ${item.check}.` });
1146
+ continue;
1147
+ }
1148
+ const result = check(resolvedArtifactPath, text);
1149
+ checks.push({ ...item, status: result.passed ? "passed" : "failed", message: result.message });
1150
+ }
1151
+ const failed = checks.filter(({ status }) => status !== "passed");
1152
+ return {
1153
+ schemaVersion: 1,
1154
+ artifactPath: resolvedArtifactPath,
1155
+ status: failed.length === 0 ? "passed" : "failed",
1156
+ checks,
1157
+ decisions,
1158
+ outOfScope,
1159
+ summary: {
1160
+ passed: checks.filter(({ status }) => status === "passed").length,
1161
+ failed: failed.length,
1162
+ decisions: decisions.length,
1163
+ outOfScope: outOfScope.length
1164
+ }
1165
+ };
1166
+ }