periplus-map 0.2.1__py3-none-any.whl

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 (272) hide show
  1. periplus/__init__.py +70 -0
  2. periplus/checkmap.py +80 -0
  3. periplus/cli.py +658 -0
  4. periplus/contract/schema/map.schema.json +138 -0
  5. periplus/contract/schema/navigation.schema.json +200 -0
  6. periplus/contract/schema/pack-file.schema.json +1405 -0
  7. periplus/contract/schema/pack-manifest.schema.json +245 -0
  8. periplus/contract/schema/project-settings.schema.json +85 -0
  9. periplus/contract/spec/engine.md +151 -0
  10. periplus/contract/spec/rules.md +352 -0
  11. periplus/detect.py +228 -0
  12. periplus/engine/__init__.py +5 -0
  13. periplus/engine/check.py +481 -0
  14. periplus/engine/emit.py +368 -0
  15. periplus/engine/packload.py +2321 -0
  16. periplus/engine/pattern.py +27 -0
  17. periplus/engine/rules.py +1946 -0
  18. periplus/engine/select.py +491 -0
  19. periplus/engine/source.py +172 -0
  20. periplus/engine/tree.py +424 -0
  21. periplus/errors.py +332 -0
  22. periplus/init.py +358 -0
  23. periplus/layout.py +28 -0
  24. periplus/manifest.py +498 -0
  25. periplus/map.py +364 -0
  26. periplus/navigate/__init__.py +1 -0
  27. periplus/navigate/find.py +143 -0
  28. periplus/navigate/ids.py +30 -0
  29. periplus/navigate/listing.py +42 -0
  30. periplus/navigate/mapfile.py +189 -0
  31. periplus/navigate/path.py +175 -0
  32. periplus/navigate/show.py +175 -0
  33. periplus/navigate/types.py +83 -0
  34. periplus/navigate/walk.py +138 -0
  35. periplus/packs/advancedqueue_basic@0.0.1/pack.yaml +14 -0
  36. periplus/packs/advancedqueue_basic@0.0.1/queue_job_type/queue_job_type.yaml +9 -0
  37. periplus/packs/advancedqueue_basic@0.0.1/queue_job_type/queue_job_type_annotation.yaml +43 -0
  38. periplus/packs/advancedqueue_basic@0.0.1/queue_job_type/queue_job_type_attribute.yaml +43 -0
  39. periplus/packs/ai_basic@0.0.1/ai_agent/ai_agent.yaml +8 -0
  40. periplus/packs/ai_basic@0.0.1/ai_agent/ai_agent_attribute.yaml +43 -0
  41. periplus/packs/ai_basic@0.0.1/ai_function_call/ai_function_call.yaml +8 -0
  42. periplus/packs/ai_basic@0.0.1/ai_function_call/ai_function_call_attribute.yaml +43 -0
  43. periplus/packs/ai_basic@0.0.1/pack.yaml +15 -0
  44. periplus/packs/config_pages_basic@0.0.1/bundle/config_pages_bundle_config.yaml +54 -0
  45. periplus/packs/config_pages_basic@0.0.1/pack.yaml +11 -0
  46. periplus/packs/crop_basic@0.0.1/bundle/crop_bundle_config.yaml +54 -0
  47. periplus/packs/crop_basic@0.0.1/pack.yaml +11 -0
  48. periplus/packs/drupal@0.1.0/block/block_placement.yaml +25 -0
  49. periplus/packs/drupal@0.1.0/block/block_placement_code.yaml +58 -0
  50. periplus/packs/drupal@0.1.0/block/block_placement_config.yaml +53 -0
  51. periplus/packs/drupal@0.1.0/block/block_plugin.yaml +52 -0
  52. periplus/packs/drupal@0.1.0/block/block_plugin_altered.yaml +61 -0
  53. periplus/packs/drupal@0.1.0/block/block_plugin_annotation.yaml +69 -0
  54. periplus/packs/drupal@0.1.0/block/block_plugin_attribute.yaml +56 -0
  55. periplus/packs/drupal@0.1.0/block/block_plugin_content.yaml +46 -0
  56. periplus/packs/drupal@0.1.0/block/block_plugin_derived.yaml +110 -0
  57. periplus/packs/drupal@0.1.0/block/block_plugin_deriver.yaml +30 -0
  58. periplus/packs/drupal@0.1.0/block/block_type.yaml +14 -0
  59. periplus/packs/drupal@0.1.0/block/block_type_code.yaml +43 -0
  60. periplus/packs/drupal@0.1.0/block/block_type_config.yaml +45 -0
  61. periplus/packs/drupal@0.1.0/cache/cache.yaml +18 -0
  62. periplus/packs/drupal@0.1.0/cache/cache_backend.yaml +31 -0
  63. periplus/packs/drupal@0.1.0/component/sdc.yaml +19 -0
  64. periplus/packs/drupal@0.1.0/component/sdc_definition.yaml +25 -0
  65. periplus/packs/drupal@0.1.0/component/sdc_use.yaml +25 -0
  66. periplus/packs/drupal@0.1.0/config/config_object.yaml +65 -0
  67. periplus/packs/drupal@0.1.0/display/display.yaml +32 -0
  68. periplus/packs/drupal@0.1.0/display/display_code.yaml +53 -0
  69. periplus/packs/drupal@0.1.0/display/display_config.yaml +95 -0
  70. periplus/packs/drupal@0.1.0/entity/entity.yaml +33 -0
  71. periplus/packs/drupal@0.1.0/entity/entity_class.yaml +60 -0
  72. periplus/packs/drupal@0.1.0/entity/entity_save.yaml +27 -0
  73. periplus/packs/drupal@0.1.0/entity/entity_type_annotation.yaml +85 -0
  74. periplus/packs/drupal@0.1.0/entity/entity_type_attribute.yaml +76 -0
  75. periplus/packs/drupal@0.1.0/field/field_instance_code.yaml +40 -0
  76. periplus/packs/drupal@0.1.0/field/field_instance_config.yaml +58 -0
  77. periplus/packs/drupal@0.1.0/field/field_storage.yaml +37 -0
  78. periplus/packs/drupal@0.1.0/field/field_storage_code.yaml +39 -0
  79. periplus/packs/drupal@0.1.0/field/field_storage_config.yaml +24 -0
  80. periplus/packs/drupal@0.1.0/form/form.yaml +48 -0
  81. periplus/packs/drupal@0.1.0/form/form_class.yaml +50 -0
  82. periplus/packs/drupal@0.1.0/form/form_id.yaml +48 -0
  83. periplus/packs/drupal@0.1.0/form/form_methods.yaml +39 -0
  84. periplus/packs/drupal@0.1.0/hook/hook.yaml +8 -0
  85. periplus/packs/drupal@0.1.0/hook/hook_attribute.yaml +28 -0
  86. periplus/packs/drupal@0.1.0/module/module.yaml +8 -0
  87. periplus/packs/drupal@0.1.0/module/module_info.yaml +50 -0
  88. periplus/packs/drupal@0.1.0/pack.yaml +184 -0
  89. periplus/packs/drupal@0.1.0/plugin/plugin.yaml +17 -0
  90. periplus/packs/drupal@0.1.0/plugin/plugin_annotation.yaml +120 -0
  91. periplus/packs/drupal@0.1.0/recommendations/recommendations.yaml +14 -0
  92. periplus/packs/drupal@0.1.0/service/service.yaml +19 -0
  93. periplus/packs/drupal@0.1.0/service/service_autowire.yaml +28 -0
  94. periplus/packs/drupal@0.1.0/service/service_definition.yaml +24 -0
  95. periplus/packs/drupal@0.1.0/service/service_use.yaml +43 -0
  96. periplus/packs/drupal_basic@0.3.0/action/action.yaml +9 -0
  97. periplus/packs/drupal_basic@0.3.0/action/action_annotation.yaml +43 -0
  98. periplus/packs/drupal_basic@0.3.0/action/action_attribute.yaml +43 -0
  99. periplus/packs/drupal_basic@0.3.0/block/block_placement.yaml +16 -0
  100. periplus/packs/drupal_basic@0.3.0/block/block_placement_config.yaml +51 -0
  101. periplus/packs/drupal_basic@0.3.0/block_plugin/block_plugin.yaml +14 -0
  102. periplus/packs/drupal_basic@0.3.0/block_plugin/block_plugin_annotation.yaml +47 -0
  103. periplus/packs/drupal_basic@0.3.0/block_plugin/block_plugin_attribute.yaml +43 -0
  104. periplus/packs/drupal_basic@0.3.0/block_type/block_type.yaml +8 -0
  105. periplus/packs/drupal_basic@0.3.0/block_type/block_type_config.yaml +54 -0
  106. periplus/packs/drupal_basic@0.3.0/boundary/boundary_types.yaml +19 -0
  107. periplus/packs/drupal_basic@0.3.0/bundle/bundle.yaml +20 -0
  108. periplus/packs/drupal_basic@0.3.0/bundle/bundle_config.yaml +393 -0
  109. periplus/packs/drupal_basic@0.3.0/call/call.yaml +349 -0
  110. periplus/packs/drupal_basic@0.3.0/component/component.yaml +30 -0
  111. periplus/packs/drupal_basic@0.3.0/config/config_object.yaml +119 -0
  112. periplus/packs/drupal_basic@0.3.0/constraint/constraint.yaml +8 -0
  113. periplus/packs/drupal_basic@0.3.0/constraint/constraint_annotation.yaml +43 -0
  114. periplus/packs/drupal_basic@0.3.0/constraint/constraint_attribute.yaml +43 -0
  115. periplus/packs/drupal_basic@0.3.0/display/display.yaml +30 -0
  116. periplus/packs/drupal_basic@0.3.0/display/display_config.yaml +203 -0
  117. periplus/packs/drupal_basic@0.3.0/display/display_mode_config.yaml +60 -0
  118. periplus/packs/drupal_basic@0.3.0/entity_type/config_entity_type_annotation.yaml +46 -0
  119. periplus/packs/drupal_basic@0.3.0/entity_type/config_entity_type_attribute.yaml +43 -0
  120. periplus/packs/drupal_basic@0.3.0/entity_type/content_entity_type_annotation.yaml +46 -0
  121. periplus/packs/drupal_basic@0.3.0/entity_type/content_entity_type_attribute.yaml +43 -0
  122. periplus/packs/drupal_basic@0.3.0/entity_type/entity_type.yaml +16 -0
  123. periplus/packs/drupal_basic@0.3.0/field/field_instance_config.yaml +116 -0
  124. periplus/packs/drupal_basic@0.3.0/field/field_storage.yaml +21 -0
  125. periplus/packs/drupal_basic@0.3.0/field/field_storage_config.yaml +45 -0
  126. periplus/packs/drupal_basic@0.3.0/field_formatter/field_formatter.yaml +8 -0
  127. periplus/packs/drupal_basic@0.3.0/field_formatter/field_formatter_annotation.yaml +46 -0
  128. periplus/packs/drupal_basic@0.3.0/field_formatter/field_formatter_attribute.yaml +43 -0
  129. periplus/packs/drupal_basic@0.3.0/field_type/field_type_annotation.yaml +46 -0
  130. periplus/packs/drupal_basic@0.3.0/field_type/field_type_attribute.yaml +50 -0
  131. periplus/packs/drupal_basic@0.3.0/field_widget/field_widget_annotation.yaml +46 -0
  132. periplus/packs/drupal_basic@0.3.0/field_widget/field_widget_attribute.yaml +50 -0
  133. periplus/packs/drupal_basic@0.3.0/form/form_id.yaml +80 -0
  134. periplus/packs/drupal_basic@0.3.0/hook/hook.yaml +100 -0
  135. periplus/packs/drupal_basic@0.3.0/hook/hook_attribute.yaml +64 -0
  136. periplus/packs/drupal_basic@0.3.0/hook/hook_theme_include.yaml +34 -0
  137. periplus/packs/drupal_basic@0.3.0/library/library.yaml +12 -0
  138. periplus/packs/drupal_basic@0.3.0/library/library_definition.yaml +58 -0
  139. periplus/packs/drupal_basic@0.3.0/local_action/local_action.yaml +12 -0
  140. periplus/packs/drupal_basic@0.3.0/local_action/local_action_definition.yaml +49 -0
  141. periplus/packs/drupal_basic@0.3.0/mail/mail.yaml +8 -0
  142. periplus/packs/drupal_basic@0.3.0/mail/mail_attribute.yaml +43 -0
  143. periplus/packs/drupal_basic@0.3.0/media_source/media_source.yaml +8 -0
  144. periplus/packs/drupal_basic@0.3.0/media_source/media_source_annotation.yaml +43 -0
  145. periplus/packs/drupal_basic@0.3.0/media_source/media_source_attribute.yaml +79 -0
  146. periplus/packs/drupal_basic@0.3.0/menu_link/menu_link.yaml +18 -0
  147. periplus/packs/drupal_basic@0.3.0/menu_link/menu_link_definition.yaml +48 -0
  148. periplus/packs/drupal_basic@0.3.0/module/module.yaml +7 -0
  149. periplus/packs/drupal_basic@0.3.0/module/module_info.yaml +58 -0
  150. periplus/packs/drupal_basic@0.3.0/pack.yaml +528 -0
  151. periplus/packs/drupal_basic@0.3.0/permission/permission.yaml +7 -0
  152. periplus/packs/drupal_basic@0.3.0/permission/permission_definition.yaml +27 -0
  153. periplus/packs/drupal_basic@0.3.0/queue_worker/queue_worker.yaml +9 -0
  154. periplus/packs/drupal_basic@0.3.0/queue_worker/queue_worker_annotation.yaml +43 -0
  155. periplus/packs/drupal_basic@0.3.0/queue_worker/queue_worker_attribute.yaml +43 -0
  156. periplus/packs/drupal_basic@0.3.0/rest_resource/rest_resource.yaml +8 -0
  157. periplus/packs/drupal_basic@0.3.0/rest_resource/rest_resource_annotation.yaml +43 -0
  158. periplus/packs/drupal_basic@0.3.0/rest_resource/rest_resource_attribute.yaml +43 -0
  159. periplus/packs/drupal_basic@0.3.0/role/role.yaml +14 -0
  160. periplus/packs/drupal_basic@0.3.0/role/role_config.yaml +44 -0
  161. periplus/packs/drupal_basic@0.3.0/route/route.yaml +42 -0
  162. periplus/packs/drupal_basic@0.3.0/route/route_definition.yaml +113 -0
  163. periplus/packs/drupal_basic@0.3.0/route/route_options.yaml +55 -0
  164. periplus/packs/drupal_basic@0.3.0/route/route_requirements.yaml +108 -0
  165. periplus/packs/drupal_basic@0.3.0/service/service.yaml +56 -0
  166. periplus/packs/drupal_basic@0.3.0/service/service_definition.yaml +181 -0
  167. periplus/packs/drupal_basic@0.3.0/template/template.yaml +86 -0
  168. periplus/packs/drupal_basic@0.3.0/template/template_route_link.yaml +30 -0
  169. periplus/packs/drupal_basic@0.3.0/theme/theme.yaml +15 -0
  170. periplus/packs/drupal_basic@0.3.0/theme/theme_info.yaml +61 -0
  171. periplus/packs/drupal_basic@0.3.0/theme_hook/template_suggestion.yaml +29 -0
  172. periplus/packs/drupal_basic@0.3.0/theme_hook/theme_hook.yaml +87 -0
  173. periplus/packs/drupal_basic@0.3.0/theme_hook/theme_hook_render.yaml +20 -0
  174. periplus/packs/drupal_basic@0.3.0/url/url.yaml +88 -0
  175. periplus/packs/drupal_basic@0.3.0/views_plugin/views_field_attribute.yaml +80 -0
  176. periplus/packs/drupal_basic@0.3.0/views_plugin/views_filter_attribute.yaml +80 -0
  177. periplus/packs/drupal_basic@0.3.0/views_plugin/views_plugin.yaml +15 -0
  178. periplus/packs/drupal_basic@0.3.0/views_plugin/views_style_attribute.yaml +80 -0
  179. periplus/packs/drupal_js_basic@0.0.1/attachment/attachment.yaml +12 -0
  180. periplus/packs/drupal_js_basic@0.0.1/attachment/attachment_in_php.yaml +15 -0
  181. periplus/packs/drupal_js_basic@0.0.1/attachment/attachment_in_php_array.yaml +52 -0
  182. periplus/packs/drupal_js_basic@0.0.1/attachment/attachment_in_twig.yaml +26 -0
  183. periplus/packs/drupal_js_basic@0.0.1/attachment/php_function_holder.yaml +13 -0
  184. periplus/packs/drupal_js_basic@0.0.1/behavior/behavior.yaml +198 -0
  185. periplus/packs/drupal_js_basic@0.0.1/pack.yaml +153 -0
  186. periplus/packs/drupal_js_basic@0.0.1/setting/setting.yaml +13 -0
  187. periplus/packs/drupal_js_basic@0.0.1/setting/setting_in_js.yaml +15 -0
  188. periplus/packs/drupal_js_basic@0.0.1/setting/setting_in_php.yaml +92 -0
  189. periplus/packs/drush_basic@0.0.1/command/command.yaml +15 -0
  190. periplus/packs/drush_basic@0.0.1/command/command_annotation.yaml +60 -0
  191. periplus/packs/drush_basic@0.0.1/command/command_attribute.yaml +88 -0
  192. periplus/packs/drush_basic@0.0.1/pack.yaml +29 -0
  193. periplus/packs/eck_basic@0.0.1/bundle/eck_bundle_config.yaml +56 -0
  194. periplus/packs/eck_basic@0.0.1/entity_type/eck_entity_type_config.yaml +26 -0
  195. periplus/packs/eck_basic@0.0.1/pack.yaml +28 -0
  196. periplus/packs/go@0.0.2/interfaces/satisfies.yaml +47 -0
  197. periplus/packs/go@0.0.2/pack.yaml +51 -0
  198. periplus/packs/go@0.0.2/types/find_declarations.yaml +137 -0
  199. periplus/packs/go@0.0.2/types/node_types.yaml +36 -0
  200. periplus/packs/go_basic@0.0.3/pack.yaml +104 -0
  201. periplus/packs/go_basic@0.0.3/rules/calls.yaml +25 -0
  202. periplus/packs/go_basic@0.0.3/rules/declarations.yaml +40 -0
  203. periplus/packs/go_basic@0.0.3/rules/imports.yaml +16 -0
  204. periplus/packs/go_basic@0.0.3/types/node_types.yaml +15 -0
  205. periplus/packs/js_basic@0.0.1/function/function.yaml +68 -0
  206. periplus/packs/js_basic@0.0.1/pack.yaml +50 -0
  207. periplus/packs/laravel_basic@0.0.5/pack.yaml +313 -0
  208. periplus/packs/laravel_basic@0.0.5/rules/classes.yaml +42 -0
  209. periplus/packs/laravel_basic@0.0.5/rules/config.yaml +229 -0
  210. periplus/packs/laravel_basic@0.0.5/rules/routes.yaml +210 -0
  211. periplus/packs/laravel_basic@0.0.5/rules/types.yaml +50 -0
  212. periplus/packs/laravel_basic@0.0.5/rules/views.yaml +100 -0
  213. periplus/packs/paragraphs_basic@0.0.1/bundle/paragraph_bundle_config.yaml +54 -0
  214. periplus/packs/paragraphs_basic@0.0.1/pack.yaml +12 -0
  215. periplus/packs/php@0.1.0/edges/edge_kinds.yaml +64 -0
  216. periplus/packs/php@0.1.0/edges/find_calls.yaml +34 -0
  217. periplus/packs/php@0.1.0/edges/find_imports.yaml +18 -0
  218. periplus/packs/php@0.1.0/edges/find_member_access.yaml +22 -0
  219. periplus/packs/php@0.1.0/edges/find_structure.yaml +38 -0
  220. periplus/packs/php@0.1.0/embedded/embedded_regions.yaml +19 -0
  221. periplus/packs/php@0.1.0/imports/imports.yaml +20 -0
  222. periplus/packs/php@0.1.0/pack.yaml +117 -0
  223. periplus/packs/php@0.1.0/resolution/excluded_tokens.yaml +23 -0
  224. periplus/packs/php@0.1.0/resolution/resolution.yaml +35 -0
  225. periplus/packs/php@0.1.0/types/find_declarations.yaml +102 -0
  226. periplus/packs/php@0.1.0/types/node_types.yaml +55 -0
  227. periplus/packs/php_basic@0.2.0/edges/edge_kinds.yaml +48 -0
  228. periplus/packs/php_basic@0.2.0/edges/find_structure.yaml +750 -0
  229. periplus/packs/php_basic@0.2.0/imports/imports.yaml +19 -0
  230. periplus/packs/php_basic@0.2.0/pack.yaml +94 -0
  231. periplus/packs/php_basic@0.2.0/types/find_declarations.yaml +267 -0
  232. periplus/packs/php_basic@0.2.0/types/node_types.yaml +55 -0
  233. periplus/packs/profile_basic@0.0.1/bundle/profile_bundle_config.yaml +54 -0
  234. periplus/packs/profile_basic@0.0.1/pack.yaml +12 -0
  235. periplus/packs/salesforce_basic@0.0.1/event/salesforce_event.yaml +36 -0
  236. periplus/packs/salesforce_basic@0.0.1/pack.yaml +31 -0
  237. periplus/packs/salesforce_basic@0.0.1/salesforce_mapping/salesforce_mapping.yaml +19 -0
  238. periplus/packs/salesforce_basic@0.0.1/salesforce_mapping/salesforce_mapping_config.yaml +83 -0
  239. periplus/packs/salesforce_basic@0.0.1/salesforce_mapping_field/salesforce_mapping_field.yaml +8 -0
  240. periplus/packs/salesforce_basic@0.0.1/salesforce_mapping_field/salesforce_mapping_field_annotation.yaml +43 -0
  241. periplus/packs/twig@0.0.1/pack.yaml +26 -0
  242. periplus/packs/twig@0.0.1/types/template.yaml +57 -0
  243. periplus/packs/twig_basic@0.1.0/pack.yaml +11 -0
  244. periplus/packs/twig_tweak_basic@0.0.1/pack.yaml +27 -0
  245. periplus/packs/twig_tweak_basic@0.0.1/rules/block.yaml +20 -0
  246. periplus/packs/twig_tweak_basic@0.0.1/rules/entity.yaml +35 -0
  247. periplus/packs/twig_tweak_basic@0.0.1/rules/field.yaml +21 -0
  248. periplus/packs/twig_tweak_basic@0.0.1/rules/menu.yaml +21 -0
  249. periplus/packs/webform_basic@0.0.1/bundle/webform_submission_bundle_config.yaml +54 -0
  250. periplus/packs/webform_basic@0.0.1/pack.yaml +20 -0
  251. periplus/packs/webform_basic@0.0.1/webform_handler/webform_handler.yaml +8 -0
  252. periplus/packs/webform_basic@0.0.1/webform_handler/webform_handler_annotation.yaml +43 -0
  253. periplus/packs/yaml@0.0.1/pack.yaml +72 -0
  254. periplus/packs/yaml@0.0.1/types/find_mapping_entry.yaml +21 -0
  255. periplus/packs/yaml@0.0.1/types/yaml_types.yaml +44 -0
  256. periplus/packs/yaml_basic@0.1.0/pack.yaml +10 -0
  257. periplus/packs.py +447 -0
  258. periplus/preflight.py +102 -0
  259. periplus/py.typed +0 -0
  260. periplus/report.py +596 -0
  261. periplus/resolution.py +355 -0
  262. periplus/settings.py +617 -0
  263. periplus/settings.stub.yml +99 -0
  264. periplus/spec.py +774 -0
  265. periplus/update.py +197 -0
  266. periplus/validate.py +655 -0
  267. periplus_map-0.2.1.dist-info/METADATA +566 -0
  268. periplus_map-0.2.1.dist-info/RECORD +272 -0
  269. periplus_map-0.2.1.dist-info/WHEEL +4 -0
  270. periplus_map-0.2.1.dist-info/entry_points.txt +2 -0
  271. periplus_map-0.2.1.dist-info/licenses/LICENSE +202 -0
  272. periplus_map-0.2.1.dist-info/licenses/NOTICE +2 -0
@@ -0,0 +1,1405 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://periplus.dev/schema/0/pack-file.schema.json",
4
+ "title": "Periplus pack file",
5
+ "description": "One file inside a pack directory. Derived from 48 worked rules across 13 Drupal concepts, not from design. Every key here appears in at least one of them; nothing here is speculative.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "$schema": {
10
+ "description": "The contract this file is written against, by the `$id` of the schema above. Allowed here because the top level refuses every key it does not declare, so a file carrying the key an editor reads would otherwise be invalid for saying which rules it follows. What the key claims is which contract the file was authored against; it does not claim that anything resolves the URL today.",
11
+ "type": "string"
12
+ },
13
+ "needs": {
14
+ "description": "What this file contributes to but does not declare. Checked before any source file is opened, so a typo fails the build instead of minting a type nothing finds.",
15
+ "type": "object",
16
+ "additionalProperties": false,
17
+ "properties": {
18
+ "types": {
19
+ "type": "array",
20
+ "items": {
21
+ "$ref": "#/$defs/typeName"
22
+ }
23
+ },
24
+ "roles": {
25
+ "type": "array",
26
+ "items": {
27
+ "$ref": "#/$defs/typeName"
28
+ }
29
+ }
30
+ }
31
+ },
32
+ "node_types": {
33
+ "type": "array",
34
+ "items": {
35
+ "$ref": "#/$defs/nodeType"
36
+ }
37
+ },
38
+ "edge_kinds": {
39
+ "type": "array",
40
+ "items": {
41
+ "$ref": "#/$defs/edgeKind"
42
+ }
43
+ },
44
+ "roles": {
45
+ "type": "array",
46
+ "items": {
47
+ "$ref": "#/$defs/role"
48
+ }
49
+ },
50
+ "rules": {
51
+ "type": "array",
52
+ "items": {
53
+ "$ref": "#/$defs/rule"
54
+ }
55
+ },
56
+ "recommendations": {
57
+ "type": "array",
58
+ "items": {
59
+ "$ref": "#/$defs/recommendation"
60
+ }
61
+ },
62
+ "imports": {
63
+ "type": "array",
64
+ "items": {
65
+ "$ref": "#/$defs/importRule"
66
+ },
67
+ "description": "Import statements are facts, not just a lookup table. why: an importer that kept only the last path segment claimed `render` as a class from `use function A\\{render};` and re-pointed a sibling's `extends Render`."
68
+ },
69
+ "resolution": {
70
+ "$ref": "#/$defs/resolution",
71
+ "description": "How a written name becomes a node. Most of the damage in the graphify record came from here rather than from extraction."
72
+ },
73
+ "excluded_tokens": {
74
+ "type": "array",
75
+ "items": {
76
+ "$ref": "#/$defs/excludedTokenGroup"
77
+ },
78
+ "description": "Strings that are never type names. why: treated as types, `self`, `static` and `parent` minted one global node each, wired to a large fraction of all classes — `parent::setUp()` alone fabricated 1,698 inbound calls onto a single method."
79
+ },
80
+ "embedded_regions": {
81
+ "type": "array",
82
+ "items": {
83
+ "$ref": "#/$defs/embeddedRegion"
84
+ },
85
+ "description": "Where a file stops being one language and starts being another."
86
+ }
87
+ },
88
+ "$defs": {
89
+ "typeName": {
90
+ "description": "Namespaced by the pack that declares it. A pack may only name a type it declares or that a pack in its dependency graph declares.",
91
+ "type": "string",
92
+ "pattern": "^[a-z][a-z0-9_]*\\.[a-z][a-z0-9_]*$"
93
+ },
94
+ "nodeType": {
95
+ "type": "object",
96
+ "additionalProperties": false,
97
+ "required": [
98
+ "name"
99
+ ],
100
+ "properties": {
101
+ "name": {
102
+ "$ref": "#/$defs/typeName"
103
+ },
104
+ "parent": {
105
+ "$ref": "#/$defs/typeName"
106
+ },
107
+ "abstract": {
108
+ "type": "boolean",
109
+ "description": "Nothing matches this directly; its descendants do."
110
+ },
111
+ "container": {
112
+ "type": "boolean"
113
+ },
114
+ "id_namespace": {
115
+ "type": "string",
116
+ "description": "Prefix for every id of this type. Required because machine names are not unique across kinds: two config files in one module carry the same internal id, a form display and a view display.",
117
+ "pattern": "^[a-z][a-z0-9_.]*$"
118
+ },
119
+ "synthetic": {
120
+ "type": "boolean",
121
+ "description": "A node the engine invents, with no construct behind it. why: top-level code has no enclosing callable, so a call from module scope had no caller and the function looked dead — 18 file-caller edges out of ~195 known sites."
122
+ }
123
+ }
124
+ },
125
+ "edgeKind": {
126
+ "type": "object",
127
+ "additionalProperties": false,
128
+ "required": [
129
+ "kind",
130
+ "from",
131
+ "to"
132
+ ],
133
+ "properties": {
134
+ "kind": {
135
+ "type": "string",
136
+ "pattern": "^[a-z][a-z0-9_]*$"
137
+ },
138
+ "from": {
139
+ "$ref": "#/$defs/endpoint"
140
+ },
141
+ "to": {
142
+ "$ref": "#/$defs/endpoint"
143
+ },
144
+ "confidence": {
145
+ "$ref": "#/$defs/confidence"
146
+ },
147
+ "source": {
148
+ "enum": [
149
+ "nearest_enclosing_container"
150
+ ],
151
+ "description": "Which end the from side attaches to. why: the containment source was hardcoded to the file node across 15 language configs, so a method's container was the file rather than its class."
152
+ }
153
+ }
154
+ },
155
+ "endpoint": {
156
+ "description": "One type, or a flat list of alternatives. A combination nested inside a list is deliberately not expressible: free alternation of intersection and union makes validation NP-complete in combined complexity, and a third-party pack may write whatever the grammar allows. It can be widened later without breaking any pack written against it.",
157
+ "oneOf": [
158
+ {
159
+ "$ref": "#/$defs/typeName"
160
+ },
161
+ {
162
+ "type": "array",
163
+ "items": {
164
+ "$ref": "#/$defs/typeName"
165
+ },
166
+ "minItems": 1
167
+ }
168
+ ]
169
+ },
170
+ "role": {
171
+ "type": "object",
172
+ "additionalProperties": false,
173
+ "required": [
174
+ "name"
175
+ ],
176
+ "properties": {
177
+ "name": {
178
+ "$ref": "#/$defs/typeName"
179
+ }
180
+ },
181
+ "description": "A capability that accumulates. A node has exactly one type; a second matching type is a real collision and the build refuses. Roles never collide."
182
+ },
183
+ "rule": {
184
+ "type": "object",
185
+ "additionalProperties": false,
186
+ "required": [
187
+ "rule",
188
+ "reads",
189
+ "match",
190
+ "emits",
191
+ "confidence"
192
+ ],
193
+ "properties": {
194
+ "rule": {
195
+ "type": "string",
196
+ "pattern": "^[a-z][a-z0-9_]*$",
197
+ "description": "The rule's own name. Recorded as provenance on every node and edge it produces, and referenced by accepted_unknowns in the project settings. Named `rule` and not `id` because `id` is the extraction block below, and two `id` keys in one mapping silently discard one of them."
198
+ },
199
+ "reads": {
200
+ "enum": [
201
+ "file",
202
+ "map"
203
+ ],
204
+ "description": "file: reads one source file, in any order, cannot see other rules. map: reads what source rules produced, runs repeatedly to a fixpoint. This is the whole of the supplementary/complementary distinction and the only ordering the engine needs."
205
+ },
206
+ "in": {
207
+ "type": "array",
208
+ "items": {
209
+ "type": "string"
210
+ },
211
+ "minItems": 1,
212
+ "description": "Named folders declared by the pack and overridable in the project settings. A rule never writes a path. Required when reads is file; meaningless when reads is map."
213
+ },
214
+ "match": {
215
+ "$ref": "#/$defs/match"
216
+ },
217
+ "id": {
218
+ "$ref": "#/$defs/idBlock"
219
+ },
220
+ "emits": {
221
+ "type": "array",
222
+ "items": {
223
+ "$ref": "#/$defs/emit"
224
+ },
225
+ "minItems": 1
226
+ },
227
+ "confidence": {
228
+ "$ref": "#/$defs/confidence"
229
+ },
230
+ "values": {
231
+ "type": "object",
232
+ "propertyNames": {"pattern": "^[a-z][a-z0-9_]*$"},
233
+ "additionalProperties": {
234
+ "type": "object",
235
+ "additionalProperties": false,
236
+ "properties": {
237
+ "from": {
238
+ "oneOf": [
239
+ {"$ref": "#/$defs/listedSource"},
240
+ {"type": "object", "additionalProperties": false, "required": ["enclosing", "capture"], "properties": {"enclosing": {"type": "string"}, "capture": {"type": "string"}}}
241
+ ]
242
+ },
243
+ "join": {"type": "string", "description": "With from: {enclosing: ...}, the text between two spans' captures."},
244
+ "template": {"type": "string"},
245
+ "normalize": {"type": "array", "items": {"type": "object", "additionalProperties": false, "required": ["replace", "with"], "properties": {"replace": {"type": "string"}, "with": {"type": "string"}}}},
246
+ "case": {"enum": ["lower", "upper"]}
247
+ },
248
+ "oneOf": [{"required": ["from"]}, {"required": ["template"]}]
249
+ },
250
+ "description": "Named values of a text rule, in order: each from one capture, from {enclosing: <span>, capture: <capture>}, or a template over captures, path values and the values before it, then normalize, then case. An enclosing value is the capture of each span of its file type with that name holding the match's start, outermost first, joined with join; a span whose capture is absent or empty adds nothing, and no such span gives the empty value. A value whose capture did not take part, or whose template names an absent value, is absent. The id, an attribute and an edge end may name a value."
251
+ },
252
+ "line": {
253
+ "type": "object",
254
+ "additionalProperties": false,
255
+ "required": ["capture"],
256
+ "properties": {"capture": {"type": "string"}},
257
+ "description": "On a text rule: the node, its values and what a template makes sit at the line this capture of match.text starts on, not the line the match starts on."
258
+ }
259
+ },
260
+ "allOf": [
261
+ {
262
+ "if": {
263
+ "properties": {
264
+ "reads": {
265
+ "const": "file"
266
+ }
267
+ },
268
+ "required": [
269
+ "reads"
270
+ ]
271
+ },
272
+ "then": {
273
+ "required": [
274
+ "in"
275
+ ]
276
+ }
277
+ },
278
+ {
279
+ "title": "A rule emits nodes or edges, never both",
280
+ "description": "The part of a file that makes a node is a different part from the part that makes an edge, and they sit on different lines. A rule doing both stamps one location on all of it, so an edge lands where its node was declared rather than where the relationship is written. In one real class the two `use` statements are on lines 96 and 97 and the class is on 94; a combined rule reports 94 for both traits. Separating them also lets each part carry its own confidence, and lets a pack add an edge to a node another pack found without editing that pack's rule.",
281
+ "if": {
282
+ "properties": {
283
+ "emits": {
284
+ "contains": {
285
+ "required": [
286
+ "edge"
287
+ ]
288
+ }
289
+ }
290
+ },
291
+ "required": [
292
+ "emits"
293
+ ]
294
+ },
295
+ "then": {
296
+ "properties": {
297
+ "emits": {
298
+ "items": {
299
+ "required": [
300
+ "edge"
301
+ ]
302
+ }
303
+ }
304
+ }
305
+ }
306
+ },
307
+ {
308
+ "if": {
309
+ "type": "object",
310
+ "required": [
311
+ "reads",
312
+ "emits"
313
+ ],
314
+ "properties": {
315
+ "reads": {
316
+ "const": "file"
317
+ },
318
+ "emits": {
319
+ "contains": {
320
+ "type": "object",
321
+ "required": [
322
+ "edge"
323
+ ],
324
+ "properties": {
325
+ "edge": {
326
+ "anyOf": [
327
+ {
328
+ "type": "object",
329
+ "properties": {
330
+ "from": {
331
+ "const": "this_node"
332
+ }
333
+ },
334
+ "required": [
335
+ "from"
336
+ ]
337
+ },
338
+ {
339
+ "type": "object",
340
+ "properties": {
341
+ "to": {
342
+ "const": "this_node"
343
+ }
344
+ },
345
+ "required": [
346
+ "to"
347
+ ]
348
+ }
349
+ ]
350
+ }
351
+ }
352
+ }
353
+ }
354
+ }
355
+ },
356
+ "then": {
357
+ "required": [
358
+ "id"
359
+ ]
360
+ },
361
+ "description": "A rule reading a file and naming this_node as an endpoint is naming the node its own id mints, so the id block is not optional there. A map rule is exempt: its this_node is a node already in the map. This is the constraint that would have caught the Phase 3 defect where split edge rules lost their id across 18 files."
362
+ },
363
+ {
364
+ "if": {
365
+ "required": ["match"],
366
+ "properties": {"match": {"required": ["declaration"]}}
367
+ },
368
+ "else": {
369
+ "properties": {
370
+ "id": {
371
+ "properties": {
372
+ "from": {
373
+ "oneOf": [
374
+ {"$ref": "#/$defs/listedSource"},
375
+ {"type": "array", "items": {"$ref": "#/$defs/listedSource"}, "minItems": 2}
376
+ ]
377
+ }
378
+ }
379
+ }
380
+ }
381
+ },
382
+ "description": "Only a declaration rule's id may name any source; the id of every other rule names a listed source."
383
+ }
384
+ ]
385
+ },
386
+ "confidence": {
387
+ "enum": [
388
+ "declared",
389
+ "inferred"
390
+ ],
391
+ "description": "Declared on the rule, never computed at runtime. The same regex on the same character is declared in Go, where capitalisation is visibility the compiler enforces, and inferred in React, where it is a convention people can ignore. Only the author knows which."
392
+ },
393
+ "match": {
394
+ "type": "object",
395
+ "additionalProperties": false,
396
+ "description": "Exactly one kind, plus qualifiers on it, plus a where list of conditions.",
397
+ "properties": {
398
+ "file": {
399
+ "type": "string",
400
+ "description": "A glob, relative to each folder named in `in`."
401
+ },
402
+ "ending": {
403
+ "oneOf": [
404
+ {"type": "string"},
405
+ {"type": "array", "items": {"type": "string"}, "minItems": 1}
406
+ ],
407
+ "description": "On a declaration, text or reference rule: the endings, without the dot, one of which must have claimed a file for the rule to read it. The glob in file is matched against the name with the claimed ending removed, so it cannot tell two endings apart; this can. Each must be an ending a loaded pack claims. Without it, every file of the file type is read."
408
+ },
409
+ "attribute": {
410
+ "type": "string",
411
+ "description": "Fully qualified attribute class."
412
+ },
413
+ "annotation": {
414
+ "type": "string",
415
+ "description": "Bare annotation name. It binds to a class the pack supplies, not one the file imports."
416
+ },
417
+ "static_call": {
418
+ "type": "string"
419
+ },
420
+ "method_call": {
421
+ "type": "string"
422
+ },
423
+ "array_entry": {
424
+ "type": "string"
425
+ },
426
+ "subscript_assignment": {
427
+ "type": "boolean"
428
+ },
429
+ "node_type": {
430
+ "$ref": "#/$defs/typeName",
431
+ "description": "Only with reads: map. Matches nodes already in the graph: each node a rule found, of this type or a type below it."
432
+ },
433
+ "follow": {
434
+ "type": "array",
435
+ "minItems": 1,
436
+ "items": {
437
+ "type": "object",
438
+ "additionalProperties": false,
439
+ "minProperties": 1,
440
+ "maxProperties": 1,
441
+ "properties": {
442
+ "edge": {"type": "string", "description": "Move to each node an edge of this declared kind runs to from the current node."},
443
+ "ancestors": {"type": "array", "items": {"type": "string"}, "minItems": 1, "description": "Run the steps after this one from the current node, else from the nearest node reached by edges of these declared kinds, breadth first, that gives a result: a member found on a class or else on its nearest ancestor."},
444
+ "node": {"type": "string", "description": "Move to the node whose whole id this template gives, filled from the captures before it; nothing when no rule found such a node or an edge end."},
445
+ "id_matches": {"type": "string", "description": "RE2 searched in the current node's whole id; a node it does not fit gives nothing, and its named captures join the values."}
446
+ }
447
+ },
448
+ "description": "Only with reads: map. Steps from the matched node, in order; the rule fires once for each node the last step reaches, which an edge end names matched_node. A step reads only nodes and edges that file rules made and edges that map rules made between them, never an end a map rule composed."
449
+ },
450
+ "grammar": {
451
+ "type": "string",
452
+ "description": "A second grammar nested inside the file's own. Needed for docblock annotations, which are not the file's language."
453
+ },
454
+ "argument": {
455
+ "type": "integer",
456
+ "minimum": 0,
457
+ "description": "Positional argument of the matched call."
458
+ },
459
+ "each": {
460
+ "type": "string",
461
+ "description": "A path expression addressing repeated entries inside a data file."
462
+ },
463
+ "where": {
464
+ "type": "array",
465
+ "items": {
466
+ "$ref": "#/$defs/condition"
467
+ },
468
+ "minItems": 1
469
+ },
470
+ "declaration": {
471
+ "type": "string",
472
+ "description": "A grammar node that declares a named thing. The kind a language pack uses; a domain pack matches attributes and calls instead."
473
+ },
474
+ "name_child": {
475
+ "oneOf": [
476
+ {"type": "string"},
477
+ {"type": "array", "items": {"type": "string"}, "minItems": 2, "maxItems": 2}
478
+ ],
479
+ "description": "Which child holds the declared name, by field name only: a child with no field name cannot be named here. On a declaration rule, a list is a path walked from the matched node, each step the child in that field or else the first named child of that node type, and the declared name is the leaf's text. On a reference rule, two fields: the first is the written name, and the second's text follows it once resolved."
480
+ },
481
+ "reference": {
482
+ "type": "string",
483
+ "description": "A grammar node that writes a reference to a name, such as a call. Each one is an edge from the node the nearest enclosing declaration was given, to the name its name_child writes."
484
+ },
485
+ "skip_names": {
486
+ "type": "array",
487
+ "items": {"type": "string"},
488
+ "minItems": 1,
489
+ "description": "On a reference rule: names that make no edge, no unresolved node and no fire. Each is compared, case-exact and before any resolution, with the text of the first name_child field as written; with a two-field name_child that is the first field, so listing a scope skips every member written under it. A name written with a leading separator, such as \\isset, is another text and is not skipped unless listed so. For a language construct the grammar parses as a call."
490
+ },
491
+ "whole_written": {
492
+ "type": "boolean",
493
+ "description": "On a reference rule: true makes a name whose node holds any named node of a type not in the grammar's written_names, at any depth, make nothing and be listed as skipped, as a name_child that is not a written name is. A chained call written through a call is such a name."
494
+ },
495
+ "body_child": {
496
+ "type": "string",
497
+ "description": "Which child holds the body. Differs per construct, and getting it wrong makes a whole family of declarations invisible without an error."
498
+ },
499
+ "filetype": {
500
+ "type": "string",
501
+ "description": "What kind of file this rule reads, named explicitly rather than left as a suffix on the glob. Required with `file`. It says which pack's grammar or loader handles the contents, so `.yml` and `.yaml` are one rule rather than two, and a glob can never accidentally match a file of another type that happens to share a name."
502
+ },
503
+ "text": {
504
+ "type": "string",
505
+ "description": "RE2 over the text of the matched construct, or of the file when no construct is named. A file is text, and some facts are easier to read as text than as a tree — a normalised signature, a version string, a shape a grammar does not give a node for. Named captures are available to the id block and to attributes. Beside `file` and `filetype`, each match in the file is one fire, in order."
506
+ },
507
+ "reads_sections": {
508
+ "type": "array",
509
+ "items": {"type": "string"},
510
+ "minItems": 1,
511
+ "description": "The sections of the file type, by name, in which a match of text still counts. A match that starts inside any other section is not a match."
512
+ }
513
+ },
514
+ "oneOf": [
515
+ {
516
+ "required": [
517
+ "file"
518
+ ],
519
+ "not": {
520
+ "required": [
521
+ "text"
522
+ ]
523
+ }
524
+ },
525
+ {
526
+ "required": [
527
+ "attribute"
528
+ ]
529
+ },
530
+ {
531
+ "required": [
532
+ "annotation"
533
+ ]
534
+ },
535
+ {
536
+ "required": [
537
+ "static_call"
538
+ ]
539
+ },
540
+ {
541
+ "required": [
542
+ "method_call"
543
+ ]
544
+ },
545
+ {
546
+ "required": [
547
+ "array_entry"
548
+ ]
549
+ },
550
+ {
551
+ "required": [
552
+ "subscript_assignment"
553
+ ]
554
+ },
555
+ {
556
+ "required": [
557
+ "node_type"
558
+ ]
559
+ },
560
+ {
561
+ "required": [
562
+ "declaration"
563
+ ]
564
+ },
565
+ {
566
+ "required": [
567
+ "reference"
568
+ ]
569
+ },
570
+ {
571
+ "required": [
572
+ "text"
573
+ ]
574
+ }
575
+ ],
576
+ "allOf": [
577
+ {
578
+ "if": {
579
+ "required": [
580
+ "file"
581
+ ]
582
+ },
583
+ "then": {
584
+ "required": [
585
+ "filetype"
586
+ ]
587
+ }
588
+ }
589
+ ]
590
+ },
591
+ "condition": {
592
+ "type": "object",
593
+ "additionalProperties": false,
594
+ "minProperties": 1,
595
+ "maxProperties": 1,
596
+ "description": "One condition. Two of these force reads: map, because they cannot be answered from one file.",
597
+ "properties": {
598
+ "applies_to": {
599
+ "type": "string",
600
+ "minLength": 1,
601
+ "description": "Where an attribute or annotation sits: the name of the tree node it is written on, as the rutter's grammar names it. In Drupal core attributes appear on classes about 3500 times, on methods about 2000, and on properties about 126, so this is not a class-only mechanism."
602
+ },
603
+ "name": {
604
+ "type": "string"
605
+ },
606
+ "key": {
607
+ "type": "object"
608
+ },
609
+ "has_key": {
610
+ "type": "object"
611
+ },
612
+ "has_argument": {
613
+ "type": "string"
614
+ },
615
+ "id_matches": {
616
+ "type": "string",
617
+ "description": "RE2. Named captures are available to the id block."
618
+ },
619
+ "matches": {
620
+ "type": "string"
621
+ },
622
+ "is_mapping": {
623
+ "type": "boolean",
624
+ "description": "Whether the entry's value is a mapping, an empty value counting as an empty mapping. A whole file's root is a mapping."
625
+ },
626
+ "listed_at": {
627
+ "type": "string",
628
+ "minLength": 1,
629
+ "description": "On an edge end: a key path of the same file whose list must hold the end's composed value. An end that is not an item gives no edge and no node."
630
+ },
631
+ "not_matches": {
632
+ "type": "string",
633
+ "description": "On a list-form edge end: a glob that a key of the each part must not match. Several conditions may be given; an end whose key matches any gives no edge and no node."
634
+ },
635
+ "attribute_argument": {
636
+ "type": "object"
637
+ },
638
+ "in_method_with_attribute": {
639
+ "type": "string"
640
+ },
641
+ "on_service": {
642
+ "type": "string"
643
+ },
644
+ "has_promoted_constructor_parameter": {
645
+ "type": "boolean"
646
+ },
647
+ "base_is_known_plugin": {
648
+ "type": "boolean"
649
+ },
650
+ "ancestor_reaches": {
651
+ "type": "string",
652
+ "description": "FORCES reads: map. Follows inherits and implements upward. Always a map condition, not sometimes: a class may extend a first-party base that is itself unclassified until another rule runs. Paths are always deduplicated, because a type reachable twice is not a type matched twice."
653
+ },
654
+ "contained_by_type": {
655
+ "$ref": "#/$defs/typeName",
656
+ "description": "FORCES reads: map. Follows contains backwards. Core has two methods called save, distinguished only by what their containing class is."
657
+ },
658
+ "has_child": {
659
+ "type": "string",
660
+ "description": "A required direct child of the matched node, by tree node type. Executed on a declaration rule: a match without such a child is passed. Go tells a struct from an interface this way: both are type_spec, and only the child differs."
661
+ },
662
+ "has_incoming_edge": {
663
+ "type": "string",
664
+ "description": "FORCES reads: map. An edge pointing at this node, of a declared kind."
665
+ },
666
+ "method_set_covers": {
667
+ "$ref": "#/$defs/typeName",
668
+ "description": "FORCES reads: map. Set comparison against every node of a type. Go's implicit interface satisfaction, which is the case the map bucket exists for."
669
+ },
670
+ "name_matches": {
671
+ "oneOf": [
672
+ {
673
+ "type": "string"
674
+ },
675
+ {
676
+ "type": "object",
677
+ "additionalProperties": false,
678
+ "required": ["child", "pattern"],
679
+ "properties": {
680
+ "child": {
681
+ "type": "string",
682
+ "minLength": 1,
683
+ "description": "The child whose text is tested: a field of the matched node, else its first named child of that node type."
684
+ },
685
+ "pattern": {
686
+ "type": "string",
687
+ "description": "RE2 over the whole text of that child."
688
+ }
689
+ }
690
+ }
691
+ ],
692
+ "description": "RE2 over the declared name. Executed on a tree declaration or reference rule: the whole text of the first name_child field must match, or the match is passed and is no fire. The mapping form {child, pattern} tests the named child instead, whatever its node type; a match without that child fails. The case that proves confidence belongs on the rule: `^[A-Z]` is `declared` in Go, where capitalisation is visibility the compiler enforces, and `inferred` in React, where it is a convention people can ignore. Same primitive, same character, and only the author knows which."
693
+ }
694
+ }
695
+ },
696
+ "idBlock": {
697
+ "type": "object",
698
+ "additionalProperties": false,
699
+ "description": "Where the id comes from, and how it is reshaped. Absent on a rule that only refines a type or adds an edge to an existing node.",
700
+ "properties": {
701
+ "from": {
702
+ "description": "A named source, or several when the id is composed from more than one. FieldStorageConfig::create supplies entity_type and field_name as separate arguments.",
703
+ "oneOf": [
704
+ {
705
+ "$ref": "#/$defs/idSource"
706
+ },
707
+ {
708
+ "type": "array",
709
+ "items": {
710
+ "anyOf": [
711
+ {"$ref": "#/$defs/idSource"},
712
+ {"$ref": "#/$defs/docblockSource"},
713
+ {"$ref": "#/$defs/qualifiedDocblockSource"}
714
+ ]
715
+ },
716
+ "minItems": 2
717
+ }
718
+ ]
719
+ },
720
+ "pattern": {
721
+ "type": "string",
722
+ "description": "RE2, with named captures the template may use. Executed on a tree declaration rule with one source: searched in its text, and a source it does not fit makes nothing. {name} is a path or settings value, filled as literal text."
723
+ },
724
+ "template": {
725
+ "type": "string",
726
+ "description": "Assembles captures and sources into the final id, including its namespace prefix."
727
+ },
728
+ "must_be": {
729
+ "enum": [
730
+ "string_literal"
731
+ ]
732
+ },
733
+ "resolve": {
734
+ "type": "array",
735
+ "items": {"$ref": "#/$defs/resolveStep"},
736
+ "minItems": 1,
737
+ "description": "The steps that turn the written name into a full one, in order; the first whose condition holds gives the name, and the name then takes on_miss. Without it the name is used as written."
738
+ },
739
+ "separator": {
740
+ "type": "string",
741
+ "description": "The separator bound_first_segment splits on and joins with, and prefix_with joins with."
742
+ },
743
+ "on_miss": {
744
+ "enum": [
745
+ "unresolved",
746
+ "out_of_reach"
747
+ ],
748
+ "description": "unresolved: a rule looked and failed, and the node records what it was doing. out_of_reach: the target is not code, is not in the repository, and no pack change will ever resolve it. Filing the second as the first invites somebody to fix what is not broken."
749
+ },
750
+ "normalize": {
751
+ "type": "array",
752
+ "items": {
753
+ "type": "object",
754
+ "additionalProperties": false,
755
+ "required": [
756
+ "replace",
757
+ "with"
758
+ ],
759
+ "properties": {
760
+ "replace": {
761
+ "type": "string"
762
+ },
763
+ "with": {
764
+ "type": "string"
765
+ }
766
+ }
767
+ },
768
+ "description": "Declared substitutions applied after the pattern. This is where a language's spelling variants are flattened — two spellings of one type, collapsing whitespace — as data rather than as engine code."
769
+ }
770
+ },
771
+ "anyOf": [
772
+ {
773
+ "required": [
774
+ "from"
775
+ ]
776
+ },
777
+ {
778
+ "required": [
779
+ "template"
780
+ ]
781
+ }
782
+ ]
783
+ },
784
+ "idSource": {
785
+ "description": "A source of a declaration rule's id: any name. declared_name, namespace, qualified_name, enclosing_type and enclosing_method are filled by the engine, a path value of the rule's file type or a settings value by its name, and any other name is the field of the matched node of that name, read as name_child reads it. enclosing_method is the declared name of the member of the enclosing type that holds the match, at which a rule of the pack or of a pack it depends on mints a node. In a from list, {qualified: <field>} reads that field and resolves its text through the file's imports and namespace, as a written name at a reference rule's end is; a text that resolves to nothing is used as written, and the template names it by the field name. The five engine names, then path values and settings values, shadow a node field of the same name, so such a field cannot be named. A name that is none of these and no field of the grammar is refused at load. A source absent for a match makes nothing, and the rule's report lists it once per file under skipped. Every other rule kind, and every other place a source is named, takes a listed source only.",
786
+ "anyOf": [
787
+ {
788
+ "type": "string",
789
+ "pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
790
+ },
791
+ {"$ref": "#/$defs/listedSource"}
792
+ ]
793
+ },
794
+ "qualifiedDocblockSource": {
795
+ "type": "object",
796
+ "additionalProperties": false,
797
+ "required": ["qualified"],
798
+ "properties": {"qualified": {"$ref": "#/$defs/docblockSource"}},
799
+ "description": "A docblock source whose value is resolved through the file's imports and namespace, as {qualified: <field>} resolves a field; a value that resolves to nothing is used as written."
800
+ },
801
+ "docblockSource": {
802
+ "type": "object",
803
+ "additionalProperties": false,
804
+ "required": ["docblock"],
805
+ "properties": {
806
+ "docblock": {
807
+ "type": "string",
808
+ "description": "RE2 with exactly one named group, searched in the comment that is the matched node's previous sibling, of the type the grammar's identity.comment names. The group's name is the value the template names. A match with no such comment, or one the pattern does not fit, makes nothing and no skipped row. One per id, in a from list; the template may leave its value out, and then the source is only a condition."
809
+ }
810
+ }
811
+ },
812
+ "listedSource": {
813
+ "description": "A named source from the engine's list, or one of the object forms.",
814
+ "oneOf": [
815
+ {
816
+ "enum": [
817
+ "array_value",
818
+ "declared_name",
819
+ "enclosing_method",
820
+ "enclosing_module",
821
+ "enclosing_type",
822
+ "file_stem",
823
+ "key",
824
+ "method_return_literal",
825
+ "module_path",
826
+ "namespace",
827
+ "package_dir",
828
+ "parameter_type",
829
+ "parent_directory",
830
+ "qualified_name",
831
+ "receiver_type",
832
+ "referenced_id",
833
+ "resolved_method",
834
+ "subscript_key",
835
+ "text"
836
+ ]
837
+ },
838
+ {
839
+ "type": "object",
840
+ "maxProperties": 2,
841
+ "additionalProperties": false,
842
+ "properties": {
843
+ "attribute_argument": {
844
+ "type": "string"
845
+ },
846
+ "annotation_key": {
847
+ "type": "string"
848
+ },
849
+ "argument_key": {
850
+ "type": "string"
851
+ },
852
+ "argument": {
853
+ "type": "integer",
854
+ "minimum": 0
855
+ },
856
+ "argument_class": {
857
+ "type": "string"
858
+ },
859
+ "array_key_class": {
860
+ "type": "string"
861
+ },
862
+ "capture": {
863
+ "type": "string"
864
+ },
865
+ "key": {
866
+ "type": "string"
867
+ },
868
+ "each": {
869
+ "type": "string"
870
+ },
871
+ "literal": {
872
+ "type": "string"
873
+ },
874
+ "each_key": {
875
+ "type": "string"
876
+ },
877
+ "incoming_edges": {
878
+ "type": "string"
879
+ },
880
+ "value": {
881
+ "type": "string"
882
+ },
883
+ "qualified": {
884
+ "type": "string",
885
+ "description": "Compose a value into a full id using the pack's own identity rules. An edge target naming a type must produce the same string the node rule produced, and only the identity block knows which separators go where. Executed in the from list of a tree declaration rule's id, where it names a field of the matched node."
886
+ }
887
+ }
888
+ }
889
+ ]
890
+ },
891
+ "emit": {
892
+ "type": "object",
893
+ "additionalProperties": false,
894
+ "minProperties": 1,
895
+ "maxProperties": 1,
896
+ "description": "Five kinds. node and edge and attribute make things; type and role refine a node that already exists, which is what a map rule does.",
897
+ "properties": {
898
+ "node": {
899
+ "type": "object",
900
+ "additionalProperties": false,
901
+ "required": [
902
+ "type"
903
+ ],
904
+ "properties": {
905
+ "type": {
906
+ "$ref": "#/$defs/typeName"
907
+ },
908
+ "state": {
909
+ "enum": [
910
+ "mapped",
911
+ "declared",
912
+ "referenced",
913
+ "unresolved",
914
+ "out_of_reach"
915
+ ]
916
+ }
917
+ }
918
+ },
919
+ "edge": {
920
+ "type": "object",
921
+ "additionalProperties": false,
922
+ "required": [
923
+ "kind",
924
+ "from",
925
+ "to"
926
+ ],
927
+ "properties": {
928
+ "kind": {
929
+ "type": "string"
930
+ },
931
+ "from": {
932
+ "$ref": "#/$defs/edgeEnd"
933
+ },
934
+ "to": {
935
+ "$ref": "#/$defs/edgeEnd"
936
+ },
937
+ "attributes": {
938
+ "type": "object",
939
+ "description": "Declared on the edge, not on either endpoint. map.schema.json's edge has carried an attributes field all along, so without this the output format had a field no input could legally produce. Each value is a declared extraction, not an open payload.",
940
+ "propertyNames": {
941
+ "pattern": "^[a-z][a-z0-9_]*$"
942
+ },
943
+ "additionalProperties": {
944
+ "$ref": "#/$defs/attributeValue"
945
+ }
946
+ }
947
+ }
948
+ },
949
+ "attribute": {
950
+ "type": "object",
951
+ "additionalProperties": false,
952
+ "required": [
953
+ "name"
954
+ ],
955
+ "properties": {
956
+ "name": {
957
+ "type": "string"
958
+ },
959
+ "from": {
960
+ "type": [
961
+ "object",
962
+ "string"
963
+ ]
964
+ },
965
+ "target": {
966
+ "enum": [
967
+ "this_node",
968
+ "containing_node"
969
+ ]
970
+ },
971
+ "template": {
972
+ "type": "string",
973
+ "description": "The value, filled from captures, path values and values."
974
+ }
975
+ }
976
+ },
977
+ "binding": {
978
+ "type": "object",
979
+ "additionalProperties": false,
980
+ "required": ["name", "target"],
981
+ "properties": {
982
+ "name": {"type": "string"},
983
+ "target": {"type": "string"}
984
+ },
985
+ "description": "A text rule binds a name in its file to a target, each a template over captures, path values and values; an empty or absent one binds nothing. A file's bindings are those of every binding rule on it, in file order, a later binding of a name winning. A rule with a binding emits only bindings and has no id. Bindings serve bound_first_segment and are not written to the map."
986
+ },
987
+ "type": {
988
+ "$ref": "#/$defs/typeName"
989
+ },
990
+ "role": {
991
+ "$ref": "#/$defs/typeName"
992
+ }
993
+ }
994
+ },
995
+ "resolveStep": {
996
+ "oneOf": [
997
+ {"enum": ["bound_first_segment", "as_written"]},
998
+ {
999
+ "type": "object",
1000
+ "additionalProperties": false,
1001
+ "required": ["full_if_prefixed"],
1002
+ "properties": {"full_if_prefixed": {"type": "string", "minLength": 1}}
1003
+ },
1004
+ {
1005
+ "type": "object",
1006
+ "additionalProperties": false,
1007
+ "required": ["prefix_with"],
1008
+ "properties": {"prefix_with": {"type": "string", "minLength": 1}}
1009
+ },
1010
+ {
1011
+ "type": "object",
1012
+ "additionalProperties": false,
1013
+ "required": ["prefix_bare_with"],
1014
+ "properties": {"prefix_bare_with": {"type": "string", "minLength": 1}}
1015
+ },
1016
+ {
1017
+ "type": "object",
1018
+ "additionalProperties": false,
1019
+ "required": ["lookup_last_segment_in"],
1020
+ "properties": {"lookup_last_segment_in": {"type": "string", "minLength": 1}}
1021
+ }
1022
+ ],
1023
+ "description": "full_if_prefixed: a name starting with the text is full without it. bound_first_segment: a name whose first segment the file binds takes the binding's target in its place. prefix_with: the named path value, value or namespace, the separator and the name. prefix_bare_with: as prefix_with, for a name that does not hold the separator only. lookup_last_segment_in: a name whose last segment, split at the separator, is a key of the named table that the pack or a pack it depends on declares under tables gives that key's value alone. as_written: the name. On a tree reference rule, a reference whose steps hold on no edge makes nothing, counts no fire and is listed as skipped. When the steps hold on one edge and fail on another, only the edge they hold on is made, the reference counts a fire, and no skipped row names the other edge."
1024
+ },
1025
+ "edgeEnd": {
1026
+ "description": "A node the rule already has, or an id minted the same way any id is minted. There is no separate target language: from and template are the id block, where filters, each makes one edge per entry. On a map rule, this_node is the node it matched, matched_node the node its follow steps reached, and a template end is filled from its captures.",
1027
+ "oneOf": [
1028
+ {
1029
+ "enum": [
1030
+ "this_node",
1031
+ "source_node",
1032
+ "enclosing_class",
1033
+ "enclosing_callable",
1034
+ "enclosing_method",
1035
+ "enclosing_file",
1036
+ "matched_node",
1037
+ "enclosing_declaration"
1038
+ ]
1039
+ },
1040
+ {
1041
+ "type": "object",
1042
+ "additionalProperties": false,
1043
+ "required": ["declared"],
1044
+ "properties": {
1045
+ "declared": {
1046
+ "$ref": "#/$defs/typeName",
1047
+ "description": "On an edge of a tree declaration rule: the id of the node of this type that a rule of this pack or of a pack it depends on declares at the same match. When no such rule fires there, the edge is not made and the fire is listed as skipped."
1048
+ }
1049
+ }
1050
+ },
1051
+ {
1052
+ "type": "object",
1053
+ "additionalProperties": false,
1054
+ "properties": {
1055
+ "from": {
1056
+ "oneOf": [
1057
+ {
1058
+ "$ref": "#/$defs/listedSource"
1059
+ },
1060
+ {
1061
+ "type": "array",
1062
+ "description": "The list form: one template over the sources. An empty list with a template alone gives one end per file, filled from the file's path values.",
1063
+ "items": {
1064
+ "$ref": "#/$defs/listedSource"
1065
+ }
1066
+ }
1067
+ ]
1068
+ },
1069
+ "template": {
1070
+ "type": "string"
1071
+ },
1072
+ "each": {
1073
+ "type": "boolean"
1074
+ },
1075
+ "where": {
1076
+ "type": "array",
1077
+ "items": {
1078
+ "$ref": "#/$defs/condition"
1079
+ }
1080
+ },
1081
+ "type": {
1082
+ "type": "string",
1083
+ "description": "The type the end has when no rule finds it: one of the edge kind's to types or a type below one. Without it the end takes the kind's to types."
1084
+ },
1085
+ "types": {
1086
+ "type": "array",
1087
+ "items": {"type": "string"},
1088
+ "minItems": 1,
1089
+ "description": "In place of type: the candidate types, in order. The end lands on the first whose node of the resolved name a rule maps; when none is mapped, on_miss applies and the end takes the first type."
1090
+ },
1091
+ "pattern": {
1092
+ "type": "string",
1093
+ "description": "RE2, with named captures the template may use. Present for the same reason the id block has it: the description above says from and template ARE the id block, and an endpoint that has to reshape a raw match needs somewhere to put the expression. Its absence is what made a pack invent an undeclared source key to smuggle a regex in."
1094
+ },
1095
+ "resolve": {
1096
+ "type": "array",
1097
+ "items": {"$ref": "#/$defs/resolveStep"},
1098
+ "minItems": 1,
1099
+ "description": "The steps that turn the written name into a full one, in order; the first whose condition holds gives the name, and the name then takes on_miss. Without it the name is used as written."
1100
+ },
1101
+ "separator": {
1102
+ "type": "string",
1103
+ "description": "The separator bound_first_segment splits on and joins with, and prefix_with joins with."
1104
+ },
1105
+ "on_miss": {
1106
+ "enum": ["unresolved"],
1107
+ "description": "When no rule maps a node of the composed id, the end is a node in state unresolved recording the rule, the name searched for and what was found. Without it the end is a node in state referenced."
1108
+ }
1109
+ },
1110
+ "anyOf": [
1111
+ {
1112
+ "required": [
1113
+ "from"
1114
+ ]
1115
+ },
1116
+ {
1117
+ "required": [
1118
+ "template"
1119
+ ]
1120
+ },
1121
+ {
1122
+ "required": [
1123
+ "type"
1124
+ ]
1125
+ },
1126
+ {
1127
+ "required": [
1128
+ "types"
1129
+ ]
1130
+ }
1131
+ ]
1132
+ }
1133
+ ]
1134
+ },
1135
+ "recommendation": {
1136
+ "type": "object",
1137
+ "additionalProperties": false,
1138
+ "required": [
1139
+ "topic",
1140
+ "text"
1141
+ ],
1142
+ "properties": {
1143
+ "topic": {
1144
+ "type": "string"
1145
+ },
1146
+ "text": {
1147
+ "type": "string"
1148
+ }
1149
+ },
1150
+ "description": "Advice for a reader. It never enters the map; it joins at read time."
1151
+ },
1152
+ "importRule": {
1153
+ "type": "object",
1154
+ "additionalProperties": false,
1155
+ "required": [
1156
+ "ast",
1157
+ "facts"
1158
+ ],
1159
+ "properties": {
1160
+ "ast": {
1161
+ "type": "string"
1162
+ },
1163
+ "group_ast": {
1164
+ "type": "string",
1165
+ "description": "The grouped form, `use A\\{B, C};`, where the kind keyword sits on the declaration rather than on each clause."
1166
+ },
1167
+ "clause": {
1168
+ "type": "string",
1169
+ "description": "The grammar node that binds one name, inside the declaration or inside its grouped form. Without it an engine cannot tell one binding from the next."
1170
+ },
1171
+ "facts": {
1172
+ "type": "object",
1173
+ "additionalProperties": {
1174
+ "type": "string"
1175
+ },
1176
+ "properties": {
1177
+ "target_fqn": {
1178
+ "type": "string"
1179
+ }
1180
+ },
1181
+ "description": "Named tree fields of a clause or declaration, each by a name bind refers to."
1182
+ },
1183
+ "bind": {
1184
+ "type": "object",
1185
+ "additionalProperties": false,
1186
+ "properties": {
1187
+ "name": {
1188
+ "type": "array",
1189
+ "items": {"type": "string"},
1190
+ "minItems": 1,
1191
+ "description": "The sources of the bound name, tried in order: a fact, the text of the field it names on the clause or else on the declaration; last_segment, the written path's last segment; or path, the whole written path."
1192
+ },
1193
+ "normalize": {
1194
+ "type": "array",
1195
+ "items": {"type": "object", "additionalProperties": false, "required": ["replace", "with"], "properties": {"replace": {"type": "string"}, "with": {"type": "string"}}},
1196
+ "description": "Steps run in order on the bound name, whichever source gave it, as a path value's normalize runs; the binding's target stays the written path."
1197
+ },
1198
+ "unbound_aliases": {
1199
+ "type": "array",
1200
+ "items": {"type": "string"},
1201
+ "minItems": 1,
1202
+ "description": "Texts of a fact that bind no name: a clause whose name comes from a fact written as one of these binds nothing, and the sources after it are not tried."
1203
+ },
1204
+ "prefix_from_group": {
1205
+ "type": "boolean",
1206
+ "description": "A clause of the grouped form takes the declaration's written prefix."
1207
+ },
1208
+ "strip_prefix": {
1209
+ "type": "string",
1210
+ "description": "Text stripped from the front of the written path."
1211
+ },
1212
+ "skip_when_field_set": {
1213
+ "type": "string",
1214
+ "description": "A fact: a declaration or clause where the field it names is set binds nothing."
1215
+ }
1216
+ },
1217
+ "description": "How a declaration binds names. Without it the declaration binds nothing."
1218
+ },
1219
+ "binds_through_alias": {
1220
+ "type": "boolean"
1221
+ }
1222
+ }
1223
+ },
1224
+ "resolution": {
1225
+ "type": "object",
1226
+ "additionalProperties": false,
1227
+ "properties": {
1228
+ "scope": {
1229
+ "enum": [
1230
+ "same_pack",
1231
+ "global"
1232
+ ],
1233
+ "description": "why: bare `extends Exception` in PHP bound to a TypeScript class because one global name map served every language — 12 phantom inheritance edges across 3 packages."
1234
+ },
1235
+ "match": {
1236
+ "enum": [
1237
+ "qualified_name_exact"
1238
+ ]
1239
+ },
1240
+ "never_match_by": {
1241
+ "type": "array",
1242
+ "items": {
1243
+ "enum": [
1244
+ "prefix",
1245
+ "substring",
1246
+ "case_folded_and_stripped"
1247
+ ]
1248
+ },
1249
+ "description": "why: a class whose name is a strict prefix of its siblings lost its entire fan-in — `Pivot` went from degree 4 to degree 1 once `PivotRepository` existed."
1250
+ },
1251
+ "on_multiple_definitions": {
1252
+ "enum": [
1253
+ "refuse",
1254
+ "first",
1255
+ "unresolved"
1256
+ ]
1257
+ },
1258
+ "on_zero_definitions": {
1259
+ "enum": [
1260
+ "unresolved",
1261
+ "drop"
1262
+ ],
1263
+ "description": "`unresolved` emits a node recording which rule fired and what it looked for. It never drops the edge. why: every failure in the record was silent, and the graph still looked populated."
1264
+ },
1265
+ "detect_references_independently_of_imports": {
1266
+ "type": "boolean",
1267
+ "description": "why: 158 of 240 Eloquent relationships were unlinked because the edge tracked the `use` statement rather than the reference. 140 were same-namespace, so no import existed to track."
1268
+ }
1269
+ }
1270
+ },
1271
+ "excludedTokenGroup": {
1272
+ "type": "object",
1273
+ "additionalProperties": false,
1274
+ "required": [
1275
+ "group",
1276
+ "tokens",
1277
+ "positions"
1278
+ ],
1279
+ "properties": {
1280
+ "group": {
1281
+ "type": "string"
1282
+ },
1283
+ "tokens": {
1284
+ "type": "array",
1285
+ "items": {
1286
+ "type": "string"
1287
+ },
1288
+ "minItems": 1
1289
+ },
1290
+ "positions": {
1291
+ "type": "array",
1292
+ "items": {
1293
+ "enum": [
1294
+ "type_reference",
1295
+ "callee",
1296
+ "supertype"
1297
+ ]
1298
+ },
1299
+ "minItems": 1,
1300
+ "description": "Declared per group because the failure was positional: the builtin list was applied at call sites only and never consulted by the supertype resolver."
1301
+ },
1302
+ "case_sensitive": {
1303
+ "type": "boolean"
1304
+ }
1305
+ }
1306
+ },
1307
+ "embeddedRegion": {
1308
+ "type": "object",
1309
+ "additionalProperties": false,
1310
+ "required": [
1311
+ "language",
1312
+ "open",
1313
+ "close"
1314
+ ],
1315
+ "properties": {
1316
+ "language": {
1317
+ "type": "string"
1318
+ },
1319
+ "open": {
1320
+ "type": "string"
1321
+ },
1322
+ "close": {
1323
+ "type": "string"
1324
+ },
1325
+ "line_offset": {
1326
+ "enum": [
1327
+ "preserve",
1328
+ "reset"
1329
+ ],
1330
+ "description": "preserve: line numbers continue from the host file. A region reported at line 1 of itself is unfindable in the file it came from."
1331
+ },
1332
+ "skip_when_empty": {
1333
+ "type": "boolean"
1334
+ },
1335
+ "host_constructs_inside": {
1336
+ "oneOf": [
1337
+ {
1338
+ "type": "boolean"
1339
+ },
1340
+ {
1341
+ "type": "array",
1342
+ "items": {
1343
+ "type": "string"
1344
+ }
1345
+ }
1346
+ ],
1347
+ "description": "Constructs of the host language that may appear inside the region."
1348
+ }
1349
+ }
1350
+ },
1351
+ "attributeValue": {
1352
+ "type": "object",
1353
+ "additionalProperties": false,
1354
+ "description": "How one attribute's value is extracted. The same vocabulary as $defs/idBlock and $defs/edgeEnd, because it is the same operation: name a source, optionally reshape it. It was briefly an open object with additionalProperties true, which let a pack write anything of any depth into a bag nothing validates — the exact hole the twig fix had just closed elsewhere.",
1355
+ "properties": {
1356
+ "from": {
1357
+ "$ref": "#/$defs/listedSource"
1358
+ },
1359
+ "pattern": {
1360
+ "type": "string",
1361
+ "description": "RE2, with named captures the template may use."
1362
+ },
1363
+ "template": {
1364
+ "type": "string"
1365
+ },
1366
+ "literal": {
1367
+ "type": "string",
1368
+ "description": "A fixed value, for an attribute that records what the rule is rather than what it found."
1369
+ },
1370
+ "normalize": {
1371
+ "type": "array",
1372
+ "items": {
1373
+ "type": "object",
1374
+ "additionalProperties": false,
1375
+ "required": [
1376
+ "replace",
1377
+ "with"
1378
+ ],
1379
+ "properties": {
1380
+ "replace": {
1381
+ "type": "string"
1382
+ },
1383
+ "with": {
1384
+ "type": "string"
1385
+ }
1386
+ }
1387
+ },
1388
+ "description": "Declared substitutions applied after the pattern. This is where a language's spelling variants are flattened — two spellings of one type, collapsing whitespace — as data rather than as engine code."
1389
+ }
1390
+ },
1391
+ "anyOf": [
1392
+ {
1393
+ "required": [
1394
+ "from"
1395
+ ]
1396
+ },
1397
+ {
1398
+ "required": [
1399
+ "literal"
1400
+ ]
1401
+ }
1402
+ ]
1403
+ }
1404
+ }
1405
+ }