@supportpages.io/wtfm 0.0.0-stage → 0.3.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 (275) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +23 -0
  3. package/README.md +392 -2
  4. package/dist/actions.d.ts +98 -0
  5. package/dist/actions.js +86 -0
  6. package/dist/actions.js.map +1 -0
  7. package/dist/agent-settings.d.ts +22 -0
  8. package/dist/agent-settings.js +35 -0
  9. package/dist/agent-settings.js.map +1 -0
  10. package/dist/api.d.ts +14 -0
  11. package/dist/api.js +159 -0
  12. package/dist/api.js.map +1 -0
  13. package/dist/article-link.d.ts +16 -0
  14. package/dist/article-link.js +25 -0
  15. package/dist/article-link.js.map +1 -0
  16. package/dist/artifacts.d.ts +45 -0
  17. package/dist/artifacts.js +144 -0
  18. package/dist/artifacts.js.map +1 -0
  19. package/dist/brand.d.ts +4 -0
  20. package/dist/brand.js +9 -0
  21. package/dist/brand.js.map +1 -0
  22. package/dist/bridge.d.ts +2964 -0
  23. package/dist/bridge.js +1036 -0
  24. package/dist/bridge.js.map +1 -0
  25. package/dist/capacity.d.ts +5 -0
  26. package/dist/capacity.js +11 -0
  27. package/dist/capacity.js.map +1 -0
  28. package/dist/credentials.d.ts +13 -0
  29. package/dist/credentials.js +88 -0
  30. package/dist/credentials.js.map +1 -0
  31. package/dist/development-tls.d.ts +8 -0
  32. package/dist/development-tls.js +38 -0
  33. package/dist/development-tls.js.map +1 -0
  34. package/dist/errors.d.ts +15 -0
  35. package/dist/errors.js +18 -0
  36. package/dist/errors.js.map +1 -0
  37. package/dist/export.d.ts +11 -0
  38. package/dist/export.js +91 -0
  39. package/dist/export.js.map +1 -0
  40. package/dist/hosted-operations.d.ts +108 -0
  41. package/dist/hosted-operations.js +120 -0
  42. package/dist/hosted-operations.js.map +1 -0
  43. package/dist/hosting-benefits.d.ts +66 -0
  44. package/dist/hosting-benefits.js +67 -0
  45. package/dist/hosting-benefits.js.map +1 -0
  46. package/dist/index.d.ts +2 -0
  47. package/dist/index.js +49 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/local-inventory.d.ts +22 -0
  50. package/dist/local-inventory.js +59 -0
  51. package/dist/local-inventory.js.map +1 -0
  52. package/dist/local-setup.d.ts +208 -0
  53. package/dist/local-setup.js +140 -0
  54. package/dist/local-setup.js.map +1 -0
  55. package/dist/pairing.d.ts +71 -0
  56. package/dist/pairing.js +235 -0
  57. package/dist/pairing.js.map +1 -0
  58. package/dist/preferences.d.ts +18 -0
  59. package/dist/preferences.js +44 -0
  60. package/dist/preferences.js.map +1 -0
  61. package/dist/progress.d.ts +205 -0
  62. package/dist/progress.js +224 -0
  63. package/dist/progress.js.map +1 -0
  64. package/dist/reminders.d.ts +31 -0
  65. package/dist/reminders.js +73 -0
  66. package/dist/reminders.js.map +1 -0
  67. package/dist/replace-connection.d.ts +6 -0
  68. package/dist/replace-connection.js +77 -0
  69. package/dist/replace-connection.js.map +1 -0
  70. package/dist/repository-actions.d.ts +18 -0
  71. package/dist/repository-actions.js +9 -0
  72. package/dist/repository-actions.js.map +1 -0
  73. package/dist/repository-benefits.d.ts +96 -0
  74. package/dist/repository-benefits.js +64 -0
  75. package/dist/repository-benefits.js.map +1 -0
  76. package/dist/run-update.d.ts +17 -0
  77. package/dist/run-update.js +27 -0
  78. package/dist/run-update.js.map +1 -0
  79. package/dist/runs.d.ts +541 -0
  80. package/dist/runs.js +146 -0
  81. package/dist/runs.js.map +1 -0
  82. package/dist/runtime.d.ts +12 -0
  83. package/dist/runtime.js +58 -0
  84. package/dist/runtime.js.map +1 -0
  85. package/dist/schema.d.ts +277 -0
  86. package/dist/schema.js +66 -0
  87. package/dist/schema.js.map +1 -0
  88. package/dist/server.d.ts +4 -0
  89. package/dist/server.js +298 -0
  90. package/dist/server.js.map +1 -0
  91. package/dist/session.d.ts +1714 -0
  92. package/dist/session.js +619 -0
  93. package/dist/session.js.map +1 -0
  94. package/dist/settings.d.ts +9 -0
  95. package/dist/settings.js +21 -0
  96. package/dist/settings.js.map +1 -0
  97. package/dist/sync.d.ts +495 -0
  98. package/dist/sync.js +191 -0
  99. package/dist/sync.js.map +1 -0
  100. package/dist/telemetry-scrub.d.ts +13 -0
  101. package/dist/telemetry-scrub.js +51 -0
  102. package/dist/telemetry-scrub.js.map +1 -0
  103. package/dist/telemetry.d.ts +73 -0
  104. package/dist/telemetry.js +173 -0
  105. package/dist/telemetry.js.map +1 -0
  106. package/dist/walkthroughs.d.ts +224 -0
  107. package/dist/walkthroughs.js +109 -0
  108. package/dist/walkthroughs.js.map +1 -0
  109. package/dist/workspace.d.ts +14 -0
  110. package/dist/workspace.js +127 -0
  111. package/dist/workspace.js.map +1 -0
  112. package/dist/writer-agent.d.ts +21 -0
  113. package/dist/writer-agent.js +27 -0
  114. package/dist/writer-agent.js.map +1 -0
  115. package/dist/writer-entry.d.ts +171 -0
  116. package/dist/writer-entry.js +233 -0
  117. package/dist/writer-entry.js.map +1 -0
  118. package/dist/writing-style.d.ts +7 -0
  119. package/dist/writing-style.js +52 -0
  120. package/dist/writing-style.js.map +1 -0
  121. package/engine/SYNC.json +4 -0
  122. package/engine/VERSION +1 -0
  123. package/engine/detect-project/README.md +141 -0
  124. package/engine/detect-project/SKILL.md +1421 -0
  125. package/engine/detect-project/assets/desktop/desktop-frame.css +428 -0
  126. package/engine/detect-project/assets/game/game-frame.css +132 -0
  127. package/engine/detect-project/assets/macosui/LICENSE-puppertino.txt +21 -0
  128. package/engine/detect-project/assets/macosui/VERSIONS.txt +1 -0
  129. package/engine/detect-project/assets/macosui/fonts.css +15 -0
  130. package/engine/detect-project/assets/macosui/macos-frame.css +481 -0
  131. package/engine/detect-project/assets/macosui/puppertino.css +2153 -0
  132. package/engine/detect-project/assets/mobileui/LICENSE-fonts.txt +13 -0
  133. package/engine/detect-project/assets/mobileui/LICENSE-framework7.txt +52 -0
  134. package/engine/detect-project/assets/mobileui/VERSIONS.txt +6 -0
  135. package/engine/detect-project/assets/mobileui/device-frame.css +316 -0
  136. package/engine/detect-project/assets/mobileui/f7-color-theme.mjs +1345 -0
  137. package/engine/detect-project/assets/mobileui/f7-icons-names.json +1254 -0
  138. package/engine/detect-project/assets/mobileui/fonts.css +16 -0
  139. package/engine/detect-project/assets/mobileui/framework7-components.css +39 -0
  140. package/engine/detect-project/assets/mobileui/framework7-core.css +5245 -0
  141. package/engine/detect-project/assets/mobileui/icons.css +31 -0
  142. package/engine/detect-project/assets/mobileui/md3-defaults.css +89 -0
  143. package/engine/detect-project/assets/mobileui/platforms.json +46 -0
  144. package/engine/detect-project/assets/tailwind-fallback.css +1729 -0
  145. package/engine/detect-project/assets/webtui/LICENSE-webtui.txt +28 -0
  146. package/engine/detect-project/assets/webtui/VERSIONS.txt +7 -0
  147. package/engine/detect-project/assets/webtui/terminal-frame.css +195 -0
  148. package/engine/detect-project/assets/webtui/theme-catppuccin.css +1 -0
  149. package/engine/detect-project/assets/webtui/theme-everforest.css +1 -0
  150. package/engine/detect-project/assets/webtui/theme-gruvbox.css +1 -0
  151. package/engine/detect-project/assets/webtui/theme-nord.css +1 -0
  152. package/engine/detect-project/assets/webtui/theme-vitesse.css +1 -0
  153. package/engine/detect-project/assets/webtui/themes.json +37 -0
  154. package/engine/detect-project/assets/webtui/webtui-core.css +1 -0
  155. package/engine/detect-project/assets/win32ui/7css.css +2 -0
  156. package/engine/detect-project/assets/win32ui/LICENSE-7css.txt +21 -0
  157. package/engine/detect-project/assets/win32ui/VERSIONS.txt +1 -0
  158. package/engine/detect-project/assets/win32ui/win32-frame.css +278 -0
  159. package/engine/detect-project/package-lock.json +12 -0
  160. package/engine/detect-project/package.json +10 -0
  161. package/engine/detect-project/scripts/apply_runtime_profiles.js +313 -0
  162. package/engine/detect-project/scripts/check_css_health.js +412 -0
  163. package/engine/detect-project/scripts/check_project_map.js +150 -0
  164. package/engine/detect-project/scripts/check_runtime_coverage.js +311 -0
  165. package/engine/detect-project/scripts/check_runtime_recipe_quality.js +184 -0
  166. package/engine/detect-project/scripts/classify_app_type.sh +246 -0
  167. package/engine/detect-project/scripts/classify_surface.sh +95 -0
  168. package/engine/detect-project/scripts/classify_workspace.js +39 -0
  169. package/engine/detect-project/scripts/compile_css.sh +447 -0
  170. package/engine/detect-project/scripts/detect_static.js +645 -0
  171. package/engine/detect-project/scripts/detect_structure.js +187 -0
  172. package/engine/detect-project/scripts/include_census.js +451 -0
  173. package/engine/detect-project/scripts/json_get.js +142 -0
  174. package/engine/detect-project/scripts/merge_json.js +52 -0
  175. package/engine/detect-project/scripts/recommend_model_tier.js +252 -0
  176. package/engine/detect-project/scripts/resolve_route_chains.js +135 -0
  177. package/engine/detect-project/scripts/run_css_build.sh +40 -0
  178. package/engine/detect-project/scripts/sanitize_css.js +83 -0
  179. package/engine/detect-project/scripts/test_classify_surface.js +108 -0
  180. package/engine/detect-project/scripts/test_node_helpers.js +142 -0
  181. package/engine/detect-project/scripts/test_recommend_model_tier.js +119 -0
  182. package/engine/detect-project/scripts/test_resolve_route_chains.js +182 -0
  183. package/engine/detect-project/scripts/test_runtime_coverage.js +323 -0
  184. package/engine/detect-project/scripts/test_runtime_recipe_quality.js +187 -0
  185. package/engine/detect-project/scripts/theme_overrides.js +169 -0
  186. package/engine/detect-project/scripts/write_branding.js +129 -0
  187. package/engine/generate-illustrated-article/SKILL.md +461 -0
  188. package/engine/generate-illustrated-article/contracts/desktop.md +90 -0
  189. package/engine/generate-illustrated-article/contracts/game.md +38 -0
  190. package/engine/generate-illustrated-article/contracts/label-evidence.md +38 -0
  191. package/engine/generate-illustrated-article/contracts/macos.md +79 -0
  192. package/engine/generate-illustrated-article/contracts/mobile.md +40 -0
  193. package/engine/generate-illustrated-article/contracts/terminal.md +29 -0
  194. package/engine/generate-illustrated-article/contracts/win32.md +43 -0
  195. package/engine/generate-illustrated-article/package-lock.json +366 -0
  196. package/engine/generate-illustrated-article/package.json +15 -0
  197. package/engine/generate-illustrated-article/scripts/article_blocks.js +34 -0
  198. package/engine/generate-illustrated-article/scripts/check_article_json.js +105 -0
  199. package/engine/generate-illustrated-article/scripts/emit_walkthrough_signals.js +100 -0
  200. package/engine/generate-illustrated-article/scripts/extract_images.js +187 -0
  201. package/engine/generate-illustrated-article/scripts/generate_content_images.js +376 -0
  202. package/engine/generate-illustrated-article/scripts/include_census.js +451 -0
  203. package/engine/generate-illustrated-article/scripts/inject_assets.js +1691 -0
  204. package/engine/generate-illustrated-article/scripts/jit_mockup_css.js +191 -0
  205. package/engine/generate-illustrated-article/scripts/label_evidence.js +87 -0
  206. package/engine/generate-illustrated-article/scripts/lint_article_copy.js +252 -0
  207. package/engine/generate-illustrated-article/scripts/lint_mockup_fidelity.js +2403 -0
  208. package/engine/generate-illustrated-article/scripts/polish_tickets.js +1148 -0
  209. package/engine/generate-illustrated-article/scripts/related_repos.sh +52 -0
  210. package/engine/generate-illustrated-article/scripts/render_all.js +177 -0
  211. package/engine/generate-illustrated-article/scripts/render_mockup.js +665 -0
  212. package/engine/generate-illustrated-article/scripts/render_ready.js +164 -0
  213. package/engine/generate-illustrated-article/scripts/resolve_workspace.sh +86 -0
  214. package/engine/generate-illustrated-article/scripts/runtime_region_geometry.js +55 -0
  215. package/engine/generate-illustrated-article/scripts/source_paths.js +49 -0
  216. package/engine/generate-illustrated-article/scripts/test_control_visibility.js +30 -0
  217. package/engine/generate-illustrated-article/scripts/test_emit_walkthrough_signals.js +157 -0
  218. package/engine/generate-illustrated-article/scripts/test_generate_content_images.js +168 -0
  219. package/engine/generate-illustrated-article/scripts/test_include_census.js +126 -0
  220. package/engine/generate-illustrated-article/scripts/test_label_evidence.js +49 -0
  221. package/engine/generate-illustrated-article/scripts/test_lint_article_copy.js +138 -0
  222. package/engine/generate-illustrated-article/scripts/test_lint_mockup_fidelity.js +653 -0
  223. package/engine/generate-illustrated-article/scripts/test_node_ports.js +152 -0
  224. package/engine/generate-illustrated-article/scripts/test_polish_tickets.js +481 -0
  225. package/engine/generate-illustrated-article/scripts/test_related_repos.js +64 -0
  226. package/engine/generate-illustrated-article/scripts/test_render_ready.js +85 -0
  227. package/engine/generate-illustrated-article/scripts/test_source_paths.js +68 -0
  228. package/engine/generate-illustrated-article/scripts/trace_hook.js +79 -0
  229. package/engine/generate-illustrated-article/scripts/trace_hook.sh +4 -0
  230. package/engine/generate-illustrated-article/scripts/validate_html.js +114 -0
  231. package/engine/generate-illustrated-article/scripts/watermark.js +69 -0
  232. package/install.sh +14 -0
  233. package/package.json +57 -4
  234. package/scripts/auth.mjs +33 -0
  235. package/scripts/auto-update.mjs +7 -0
  236. package/scripts/build-plugin.mjs +58 -0
  237. package/scripts/build-release.mjs +94 -0
  238. package/scripts/check-release.mjs +40 -0
  239. package/scripts/check-runtime.mjs +10 -0
  240. package/scripts/cli.mjs +8 -0
  241. package/scripts/install.mjs +64 -0
  242. package/scripts/lib/agent-runner.mjs +181 -0
  243. package/scripts/lib/agent-settings.mjs +215 -0
  244. package/scripts/lib/article-skills.mjs +82 -0
  245. package/scripts/lib/auto-update.mjs +50 -0
  246. package/scripts/lib/brand.mjs +9 -0
  247. package/scripts/lib/browser.mjs +12 -0
  248. package/scripts/lib/claude-connection.mjs +33 -0
  249. package/scripts/lib/claude-permissions.mjs +47 -0
  250. package/scripts/lib/claude-plugin.mjs +15 -0
  251. package/scripts/lib/claude-writer.mjs +10 -0
  252. package/scripts/lib/cli-main.mjs +85 -0
  253. package/scripts/lib/cli.mjs +888 -0
  254. package/scripts/lib/codex-config.mjs +51 -0
  255. package/scripts/lib/codex-integration.mjs +61 -0
  256. package/scripts/lib/codex-skill.mjs +34 -0
  257. package/scripts/lib/harness-models.mjs +109 -0
  258. package/scripts/lib/install.mjs +269 -0
  259. package/scripts/lib/managed-writer.mjs +38 -0
  260. package/scripts/lib/planning.mjs +263 -0
  261. package/scripts/lib/prepare-update.mjs +85 -0
  262. package/scripts/lib/refresh-writers.mjs +12 -0
  263. package/scripts/lib/remove.mjs +167 -0
  264. package/scripts/lib/renderer.mjs +34 -0
  265. package/scripts/lib/terminal.mjs +252 -0
  266. package/scripts/lib/uninit.mjs +72 -0
  267. package/scripts/lib/update.mjs +55 -0
  268. package/scripts/lib/writer-recovery.mjs +35 -0
  269. package/scripts/lib/yolo.mjs +362 -0
  270. package/scripts/plugin-session.mjs +65 -0
  271. package/scripts/prepare-update.mjs +12 -0
  272. package/scripts/publish-release.mjs +92 -0
  273. package/scripts/skills/supportpages/SKILL.md +120 -0
  274. package/scripts/smoke-release.mjs +101 -0
  275. package/server.json +27 -0
@@ -0,0 +1,323 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const assert = require('assert');
5
+ const fs = require('fs');
6
+ const os = require('os');
7
+ const path = require('path');
8
+ const { spawnSync } = require('child_process');
9
+
10
+ const checker = path.join(__dirname, 'check_runtime_coverage.js');
11
+
12
+ function write(file, value) {
13
+ fs.mkdirSync(path.dirname(file), { recursive: true });
14
+ fs.writeFileSync(file, typeof value === 'string' ? value : JSON.stringify(value, null, 2));
15
+ }
16
+
17
+ function run(mutator = () => {}) {
18
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'rtfm-runtime-coverage-'));
19
+ write(path.join(root, 'views/editor.php'), 'enqueue(); app.initializeEditor("editor", {}); <div id="editor"></div>');
20
+ write(path.join(root, 'views/other.php'), 'plain server rendered page');
21
+ write(path.join(root, 'views/preview.webp'), 'representative-image');
22
+ const map = {
23
+ layouts: [{ name: 'editor', path: 'views/editor.php', chrome: [], runtime_chrome: {
24
+ source_kind: 'repo', source_files: ['views/editor.php'], regions: [], states: [],
25
+ }}],
26
+ route_index: { editor: { controller: 'views/editor.php', layout: 'editor' },
27
+ other: { controller: 'views/other.php', layout: 'plain' } },
28
+ };
29
+ mutator(map, root);
30
+ write(path.join(root, 'project_map.json'), map);
31
+ const reportPath = path.join(root, 'coverage.json');
32
+ const proc = spawnSync(process.execPath, [checker, path.join(root, 'project_map.json'), root, '--json', reportPath], { encoding: 'utf8' });
33
+ const report = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
34
+ fs.rmSync(root, { recursive: true, force: true });
35
+ return { proc, report };
36
+ }
37
+
38
+ function strongSiteEditorRecipe() {
39
+ const src = ['views/editor.php'];
40
+ return {
41
+ app: 'Site editor',
42
+ mode_note: 'The editor opens with persistent navigation, toolbar, and canvas.',
43
+ source_kind: 'repo',
44
+ source_files: src,
45
+ regions: [
46
+ { id: 'toolbar', region: 'editor toolbar', placement: 'top', required: true,
47
+ appearance: 'A full-width dark toolbar above the workspace.',
48
+ items: ['site icon', 'view toggle', 'save control'], ui_strings: ['View', 'Save'],
49
+ required_strings: ['Save'], source_files: src },
50
+ { id: 'site-navigation', region: 'site navigation', placement: 'left', required: true,
51
+ appearance: 'A wide left rail of editor destinations.',
52
+ items: ['Navigation row', 'Styles row', 'Templates row'],
53
+ ui_strings: ['Navigation', 'Styles', 'Templates'], required_strings: ['Styles'], source_files: src },
54
+ { id: 'canvas', region: 'editing canvas', placement: 'canvas', required: true,
55
+ appearance: 'A dominant central preview of the site.',
56
+ items: ['site preview', 'selected content outline'], ui_strings: ['Design'],
57
+ required_strings: ['Design'], source_files: src },
58
+ { id: 'settings', region: 'editing settings', placement: 'right', required: false,
59
+ appearance: 'A settings sidebar aligned to the right edge.',
60
+ items: ['settings tabs', 'property controls'], ui_strings: ['Settings'],
61
+ required_strings: ['Settings'], source_files: src },
62
+ { id: 'save-confirmation', region: 'save confirmation', placement: 'overlay-right', required: false,
63
+ appearance: 'A raised confirmation panel over the right side of the canvas.',
64
+ items: ['changed items summary', 'cancel button', 'confirm save button'],
65
+ ui_strings: ['Review changes', 'Cancel', 'Save'],
66
+ required_strings: ['Review changes', 'Save'], source_files: src },
67
+ ],
68
+ states: [
69
+ { id: 'overview', name: 'overview', kind: 'overview', screenshot_required: false,
70
+ entered_by: 'open the editor', shows: 'the editor overview',
71
+ visible_regions: ['toolbar', 'site-navigation', 'canvas'], required_regions: [],
72
+ required_strings: [], source_files: src },
73
+ { id: 'edit-styles', name: 'edit styles', kind: 'action', screenshot_required: true,
74
+ entered_by: 'open Styles', shows: 'style controls beside the preview',
75
+ visible_regions: ['toolbar', 'site-navigation', 'canvas', 'settings'], required_regions: ['settings'],
76
+ required_strings: ['Styles'], source_files: src },
77
+ { id: 'edit-templates', name: 'edit templates', kind: 'action', screenshot_required: true,
78
+ entered_by: 'open Templates', shows: 'template controls beside the preview',
79
+ visible_regions: ['toolbar', 'site-navigation', 'canvas', 'settings'], required_regions: ['settings'],
80
+ required_strings: ['Templates'], source_files: src },
81
+ { id: 'confirm-save', name: 'confirm save', kind: 'confirm', screenshot_required: true,
82
+ entered_by: 'press Save', shows: 'the save review over the edited canvas',
83
+ visible_regions: ['toolbar', 'site-navigation', 'canvas', 'settings', 'save-confirmation'],
84
+ required_regions: ['save-confirmation'], required_strings: ['Save'], source_files: src },
85
+ ],
86
+ };
87
+ }
88
+
89
+ function runV3(recipeMutator = () => {}) {
90
+ return run(map => {
91
+ map.schema_version = 3;
92
+ map.layouts[0].runtime_chrome = strongSiteEditorRecipe();
93
+ recipeMutator(map.layouts[0].runtime_chrome, map);
94
+ });
95
+ }
96
+
97
+ function runV4(recipeMutator = () => {}) {
98
+ return run(map => {
99
+ map.schema_version = 4;
100
+ const recipe = strongSiteEditorRecipe();
101
+ for (const region of recipe.regions) {
102
+ region.items = region.items.map((description, index) => ({ id: `item-${index + 1}`, description }));
103
+ if (region.placement === 'canvas') Object.assign(region, {
104
+ content_mode: 'populated', media_expectation: 'required',
105
+ representative_assets: ['views/preview.webp'], max_selected_outlines: 1,
106
+ });
107
+ }
108
+ const priorities = ['orientation', 'primary-action', 'secondary-action', 'terminal-action'];
109
+ recipe.states.forEach((state, index) => {
110
+ state.topic_tags = [state.name];
111
+ state.instructional_priority = priorities[index];
112
+ if (state.kind === 'action') state.screenshot_required = false;
113
+ });
114
+ map.layouts[0].runtime_chrome = recipe;
115
+ recipeMutator(recipe, map);
116
+ });
117
+ }
118
+
119
+ let result = run();
120
+ assert.strictEqual(result.proc.status, 0, result.proc.stderr);
121
+ assert.strictEqual(result.report.candidates.length, 1);
122
+
123
+ result = run((map, root) => {
124
+ write(path.join(root, 'views/site.php'), 'scripts(); product.initializeApplication("site", {});');
125
+ map.route_index.site = { controller: 'views/site.php', layout: 'editor' };
126
+ });
127
+ assert.strictEqual(result.proc.status, 1);
128
+ assert(result.report.errors.some(error => error.includes('views/site.php')));
129
+
130
+ result = run((map, root) => {
131
+ write(path.join(root, 'views/site.php'), 'scripts(); product.initializeApplication("site", {});');
132
+ map.route_index.site = { controller: 'views/site.php', layout: 'editor' };
133
+ map.layouts[0].runtime_chrome.source_files.push('views/site.php');
134
+ });
135
+ assert.strictEqual(result.proc.status, 1);
136
+ assert(result.report.errors.some(error => error.includes('combines distinct mount files')));
137
+
138
+ result = run((map, root) => {
139
+ write(path.join(root, 'views/site.php'), 'scripts(); product.initializeApplication("site", {});');
140
+ map.route_index.site = { controller: 'views/site.php', layout: 'editor' };
141
+ map.runtime_surface_coverage = { ignored: [{ source_file: 'views/site.php', reason: 'progressive enhancement only' }] };
142
+ });
143
+ assert.strictEqual(result.proc.status, 0, result.proc.stderr);
144
+
145
+ // A complete schema-v3 Site Editor-shaped recipe is accepted.
146
+ result = runV3();
147
+ assert.strictEqual(result.proc.status, 0, result.proc.stderr);
148
+
149
+ // The old descriptive recipe shape cannot pass as an enforceable v3 recipe.
150
+ result = run((map) => {
151
+ map.schema_version = 3;
152
+ map.layouts[0].runtime_chrome = {
153
+ app: 'Site editor', mode_note: 'Fullscreen editor', source_kind: 'repo',
154
+ source_files: ['views/editor.php'],
155
+ regions: [
156
+ { id: 'navigation', region: 'navigation', placement: 'left', required: true,
157
+ items: ['Navigation', 'Styles', 'Templates'], ui_strings: ['Navigation', 'Styles', 'Templates'],
158
+ required_strings: ['Styles'], source_files: ['views/editor.php'] },
159
+ { id: 'canvas', region: 'canvas', placement: 'canvas', required: true,
160
+ items: ['preview'], ui_strings: ['Design'], required_strings: ['Design'], source_files: ['views/editor.php'] },
161
+ { id: 'confirm', region: 'confirmation', placement: 'overlay-right', required: false,
162
+ items: ['Save'], ui_strings: ['Save'], required_strings: ['Save'], source_files: ['views/editor.php'] },
163
+ ],
164
+ states: [
165
+ { id: 'editing', kind: 'action', screenshot_required: true, required_strings: ['Styles'] },
166
+ { id: 'confirm-save', kind: 'confirm', screenshot_required: true, required_strings: ['Save'] },
167
+ ],
168
+ };
169
+ });
170
+ assert.strictEqual(result.proc.status, 1);
171
+ assert(result.report.errors.some(error => error.includes('appearance')));
172
+ assert(result.report.errors.some(error => error.includes('visible_regions')));
173
+ assert(result.report.errors.some(error => error.includes('overlay runtime region')));
174
+
175
+ // The schema-v3 contract rejects weak IDs and inconsistent region visibility.
176
+ result = runV3(recipe => { recipe.regions[0].id = 'Bad ID'; });
177
+ assert.strictEqual(result.proc.status, 1);
178
+ assert(result.report.errors.some(error => error.includes('unique kebab-case region ids')));
179
+
180
+ result = runV3(recipe => { recipe.states[2].id = recipe.states[1].id; });
181
+ assert.strictEqual(result.proc.status, 1);
182
+ assert(result.report.errors.some(error => error.includes('unique kebab-case state ids')));
183
+
184
+ result = runV3(recipe => {
185
+ recipe.app = '';
186
+ recipe.mode_note = ' ';
187
+ recipe.regions[0].region = '';
188
+ recipe.regions[0].items = [];
189
+ recipe.regions[0].appearance = '';
190
+ });
191
+ assert.strictEqual(result.proc.status, 1);
192
+ for (const field of ['app', 'mode_note', 'region label', 'items', 'appearance']) {
193
+ assert(result.report.errors.some(error => error.includes(field)), `missing ${field} validation`);
194
+ }
195
+
196
+ result = runV3(recipe => { recipe.regions[3].required = true; });
197
+ assert.strictEqual(result.proc.status, 1);
198
+ assert(result.report.errors.some(error => error.includes("runtime region 'settings' required")));
199
+
200
+ result = runV3(recipe => { recipe.states[1].visible_regions = ['canvas', 'toolbar']; });
201
+ assert.strictEqual(result.proc.status, 1);
202
+ assert(result.report.errors.some(error => error.includes('recipe order')));
203
+
204
+ result = runV3(recipe => { recipe.states[0].screenshot_required = true; });
205
+ assert.strictEqual(result.proc.status, 1);
206
+ assert(result.report.errors.some(error => error.includes('overview state')));
207
+
208
+ result = runV3(recipe => {
209
+ recipe.states[0].kind = 'action';
210
+ recipe.states[0].screenshot_required = true;
211
+ recipe.states[0].required_strings = ['Design'];
212
+ });
213
+ assert.strictEqual(result.proc.status, 1);
214
+ assert(result.report.errors.some(error => error.includes('more than three screenshot-required states')));
215
+
216
+ result = runV3(recipe => {
217
+ recipe.states[1].visible_regions = [];
218
+ recipe.states[1].required_strings = [];
219
+ });
220
+ assert.strictEqual(result.proc.status, 1);
221
+ assert(result.report.errors.some(error => error.includes('must have visible_regions')));
222
+ assert(result.report.errors.some(error => error.includes('visible required string assertion')));
223
+
224
+ result = runV3(recipe => { recipe.regions[4].required_strings = ['Save']; });
225
+ assert.strictEqual(result.proc.status, 1);
226
+ assert(result.report.errors.some(error => error.includes('at least two required_strings')));
227
+
228
+ result = runV3(recipe => { recipe.regions[4].items = ['summary', 'save']; });
229
+ assert.strictEqual(result.proc.status, 1);
230
+ assert(result.report.errors.some(error => error.includes('at least three items')));
231
+
232
+ result = runV3(recipe => {
233
+ recipe.states = recipe.states.filter(state => state.id !== 'edit-templates');
234
+ });
235
+ assert.strictEqual(result.proc.status, 1);
236
+ assert(result.report.errors.some(error => error.includes('at least two action states')));
237
+ assert(result.report.errors.some(error => error.includes('at least two screenshot-required action states')));
238
+
239
+ result = runV3(recipe => {
240
+ recipe.source_files = ['views/other.php'];
241
+ });
242
+ assert.strictEqual(result.proc.status, 1);
243
+ assert(result.report.errors.some(error => error.includes('must include its layout mount')));
244
+
245
+ // Schema v4 adds executable item inventories, topic-aware state selection,
246
+ // and source-backed populated-canvas evidence without requiring every action
247
+ // branch to consume a screenshot slot.
248
+ result = runV4();
249
+ assert.strictEqual(result.proc.status, 0, result.proc.stderr);
250
+
251
+ result = runV4(recipe => { recipe.regions[0].items[0] = { id: 'Bad ID', description: '' }; });
252
+ assert.strictEqual(result.proc.status, 1);
253
+ assert(result.report.errors.some(error => error.includes('ordered items')));
254
+
255
+ result = runV4(recipe => { delete recipe.states[1].topic_tags; });
256
+ assert.strictEqual(result.proc.status, 1);
257
+ assert(result.report.errors.some(error => error.includes('topic_tags')));
258
+
259
+ result = runV4(recipe => { recipe.regions[2].representative_assets = []; });
260
+ assert.strictEqual(result.proc.status, 1);
261
+ assert(result.report.errors.some(error => error.includes('requires representative_assets')));
262
+
263
+ function v5Mutator(extra = () => {}) {
264
+ return (recipe, map) => {
265
+ map.schema_version = 5;
266
+ for (const region of recipe.regions) {
267
+ for (const item of region.items) Object.assign(item, { kind: 'control-group', required_strings: [] });
268
+ }
269
+ const overlay = recipe.regions.find(region => region.placement.startsWith('overlay-'));
270
+ overlay.items[0].kind = 'group-heading';
271
+ overlay.items[1].kind = 'selection-row';
272
+ overlay.items[2].kind = 'actions';
273
+ for (const state of recipe.states) {
274
+ state.canvas_presentation = 'The populated canvas fills the workspace.';
275
+ if (['action', 'confirm'].includes(state.kind)) {
276
+ state.context_label = 'Editor document';
277
+ state.required_strings.push(state.context_label);
278
+ }
279
+ }
280
+ extra(recipe, map);
281
+ };
282
+ }
283
+
284
+ result = runV4(v5Mutator());
285
+ assert.strictEqual(result.proc.status, 0, result.proc.stderr);
286
+
287
+ result = runV4(v5Mutator(recipe => { delete recipe.regions[0].items[0].kind; }));
288
+ assert.strictEqual(result.proc.status, 1);
289
+ assert(result.report.errors.some(error => error.includes('items require kebab-case kind')));
290
+
291
+ result = runV4(v5Mutator(recipe => { recipe.states[1].context_label = 'Missing from assertions'; }));
292
+ assert.strictEqual(result.proc.status, 1);
293
+ assert(result.report.errors.some(error => error.includes('concrete context_label')));
294
+
295
+ result = runV4(v5Mutator(recipe => { recipe.regions[4].items[1].description = 'No changes'; }));
296
+ assert.strictEqual(result.proc.status, 1);
297
+ assert(result.report.errors.some(error => error.includes('concrete changed entities')));
298
+
299
+ // Ignore entries are typed, unique, and mutually exclusive with ownership.
300
+ result = run((map) => {
301
+ map.runtime_surface_coverage = { ignored: 'views/editor.php' };
302
+ });
303
+ assert.strictEqual(result.proc.status, 1);
304
+ assert(result.report.errors.some(error => error.includes('ignored must be an array')));
305
+
306
+ result = run((map) => {
307
+ map.layouts = [];
308
+ map.runtime_surface_coverage = { ignored: [{ source_file: 'views/editor.php', reason: 'x' }] };
309
+ });
310
+ assert.strictEqual(result.proc.status, 1);
311
+ assert(result.report.errors.some(error => error.includes('specific reason')));
312
+
313
+ result = run((map) => {
314
+ map.runtime_surface_coverage = { ignored: [
315
+ { source_file: 'views/editor.php', reason: 'progressive enhancement only' },
316
+ { source_file: 'views/editor.php', reason: 'duplicate progressive enhancement' },
317
+ ] };
318
+ });
319
+ assert.strictEqual(result.proc.status, 1);
320
+ assert(result.report.errors.some(error => error.includes('duplicate source_file')));
321
+ assert(result.report.errors.some(error => error.includes('both owned and ignored')));
322
+
323
+ console.log('runtime surface coverage tests passed');
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const assert = require('assert');
5
+ const fs = require('fs');
6
+ const os = require('os');
7
+ const path = require('path');
8
+ const { spawnSync } = require('child_process');
9
+
10
+ const checker = path.join(__dirname, 'check_runtime_recipe_quality.js');
11
+ const profiles = path.join(__dirname, 'apply_runtime_profiles.js');
12
+ const coverage = path.join(__dirname, 'check_runtime_coverage.js');
13
+
14
+ function write(file, value) {
15
+ fs.mkdirSync(path.dirname(file), { recursive: true });
16
+ fs.writeFileSync(file, typeof value === 'string' ? value : `${JSON.stringify(value, null, 2)}\n`);
17
+ }
18
+
19
+ function files(root) {
20
+ const values = {
21
+ 'views/editor.js': 'app.initializeEditor("editor", {});',
22
+ 'views/template.html': '<main>Blog Home</main>',
23
+ 'views/header.html': '<header>My Site</header>',
24
+ 'views/footer.html': '<footer>Designed with WordPress</footer>',
25
+ 'views/preview.webp': 'image',
26
+ };
27
+ for (const [rel, value] of Object.entries(values)) write(path.join(root, rel), value);
28
+ }
29
+
30
+ function strongMap() {
31
+ const mount = ['views/editor.js'];
32
+ const content = ['views/template.html', 'views/header.html', 'views/footer.html'];
33
+ return {
34
+ schema_version: 5,
35
+ default_user_assumptions: ['JavaScript enabled'],
36
+ app_shell: { root_classes: { html: ['with-toolbar'], body: ['app-shell'] } },
37
+ runtime_surface_coverage: { ignored: [] },
38
+ layouts: [{
39
+ name: 'site-editor', path: 'views/editor.js', area: 'fullscreen editor with no classic toolbar', chrome: [],
40
+ root_classes: { html: [], body: ['editor', 'is-fullscreen-mode'] },
41
+ runtime_chrome: {
42
+ app: 'Site editor', mode_note: 'Always fullscreen with the shell hidden.',
43
+ source_kind: 'knowledge', source_files: mount,
44
+ regions: [
45
+ { id: 'navigation', region: 'design navigation', placement: 'left', required: false,
46
+ appearance: 'dark #111 sidebar',
47
+ items: [{ id: 'identity', kind: 'identity', description: 'site identity', required_strings: [] },
48
+ { id: 'destinations', kind: 'navigation-list', description: 'destinations', required_strings: ['Styles', 'Templates'] }],
49
+ ui_strings: ['Styles', 'Templates'], required_strings: ['Styles'], source_files: mount },
50
+ { id: 'toolbar', region: 'editing toolbar', placement: 'top', required: false,
51
+ appearance: 'light full-width toolbar',
52
+ items: [{ id: 'identity', kind: 'identity', description: 'back identity', required_strings: [] },
53
+ { id: 'editing-tools', kind: 'control-group', description: 'editing controls', required_strings: [] },
54
+ { id: 'document-title', kind: 'document-context', description: 'document title', required_strings: ['Blog Home'] },
55
+ { id: 'view-tools', kind: 'control-group', description: 'view controls', required_strings: [] },
56
+ { id: 'save', kind: 'primary-action', description: 'save action', required_strings: ['Save'] }],
57
+ ui_strings: ['Blog Home', 'Save'], required_strings: ['Save'], source_files: mount },
58
+ { id: 'canvas', region: 'site canvas', placement: 'canvas', required: true,
59
+ appearance: 'largest white populated canvas', content_mode: 'populated', media_expectation: 'required',
60
+ representative_assets: ['views/preview.webp'], max_selected_outlines: 1,
61
+ items: [{ id: 'header', kind: 'content-section', description: 'header', required_strings: [] },
62
+ { id: 'main', kind: 'content-section', description: 'main content', required_strings: [] },
63
+ { id: 'footer', kind: 'content-section', description: 'footer', required_strings: ['Designed with WordPress'] }],
64
+ ui_strings: ['Designed with WordPress'], required_strings: ['Designed with WordPress'], source_files: content },
65
+ { id: 'styles-panel', region: 'styles inspector', placement: 'right', required: false,
66
+ appearance: 'light right sidebar',
67
+ items: [{ id: 'heading', kind: 'panel-heading', description: 'heading', required_strings: ['Styles'] },
68
+ { id: 'browse', kind: 'action-card', description: 'browse styles', required_strings: ['Browse styles'] },
69
+ { id: 'colors', kind: 'panel-row', description: 'colors row', required_strings: ['Colors'] },
70
+ { id: 'layout', kind: 'panel-row', description: 'layout row', required_strings: ['Layout'] }],
71
+ ui_strings: ['Styles', 'Browse styles', 'Colors', 'Layout'], required_strings: ['Styles'], source_files: mount },
72
+ { id: 'template-panel', region: 'template inspector', placement: 'right', required: false,
73
+ appearance: 'light right sidebar',
74
+ items: [{ id: 'heading', kind: 'panel-heading', description: 'heading', required_strings: ['Template'] },
75
+ { id: 'summary', kind: 'settings-list', description: 'summary', required_strings: ['Blog Home'] },
76
+ { id: 'header', kind: 'panel-row', description: 'header row', required_strings: ['Header'] },
77
+ { id: 'footer', kind: 'panel-row', description: 'footer row', required_strings: ['Footer'] }],
78
+ ui_strings: ['Template', 'Blog Home', 'Header', 'Footer'], required_strings: ['Template'], source_files: mount },
79
+ { id: 'confirm', region: 'save confirmation', placement: 'overlay-right', required: false,
80
+ appearance: 'light right overlay panel',
81
+ items: [{ id: 'heading', kind: 'panel-heading', description: 'heading', required_strings: ['Ready to save?'] },
82
+ { id: 'styles-group', kind: 'group-heading', description: 'styles group', required_strings: ['Styles'] },
83
+ { id: 'styles-change', kind: 'selection-row', description: 'theme change', required_strings: ['Example Theme'] },
84
+ { id: 'templates-group', kind: 'group-heading', description: 'templates group', required_strings: ['Templates'] },
85
+ { id: 'template-change', kind: 'selection-row', description: 'template change', required_strings: ['Blog Home'] },
86
+ { id: 'actions', kind: 'actions', description: 'actions', required_strings: ['Cancel', 'Save'] }],
87
+ ui_strings: ['Ready to save?', 'Styles', 'Templates', 'Cancel', 'Save'],
88
+ required_strings: ['Ready to save?', 'Cancel', 'Save'], source_files: mount },
89
+ ],
90
+ states: [
91
+ { id: 'overview', kind: 'overview', visible_regions: ['navigation', 'canvas'], required_regions: [],
92
+ screenshot_required: false, required_strings: ['Styles'], source_files: content },
93
+ { id: 'styles-editing', kind: 'action', context_label: 'Blog Home', visible_regions: ['toolbar', 'canvas', 'styles-panel'],
94
+ required_regions: ['toolbar', 'styles-panel'], screenshot_required: false, required_strings: ['Blog Home'], source_files: content },
95
+ { id: 'template-editing', kind: 'action', context_label: 'Blog Home', visible_regions: ['toolbar', 'canvas', 'template-panel'],
96
+ required_regions: ['toolbar', 'template-panel'], screenshot_required: false, required_strings: ['Blog Home'], source_files: content },
97
+ { id: 'confirm-save', kind: 'confirm', context_label: 'Blog Home', visible_regions: ['toolbar', 'canvas', 'confirm'],
98
+ required_regions: ['confirm'], screenshot_required: true, required_strings: ['Blog Home', 'Save'], source_files: content },
99
+ ],
100
+ },
101
+ }],
102
+ route_index: { editor: { path: '/editor', controller: 'views/editor.js', primary_view: 'views/editor.js',
103
+ controller_action: 'views/editor.js', layout: 'site-editor', partials_expanded: content } },
104
+ };
105
+ }
106
+
107
+ function runQuality(mutator = () => {}) {
108
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'rtfm-runtime-quality-'));
109
+ files(root);
110
+ const map = strongMap();
111
+ mutator(map, root);
112
+ const mapPath = path.join(root, 'project_map.json');
113
+ write(mapPath, map);
114
+ const reportPath = path.join(root, 'report.json');
115
+ const proc = spawnSync(process.execPath, [checker, mapPath, root, '--json', reportPath], { encoding: 'utf8' });
116
+ const report = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
117
+ fs.rmSync(root, { recursive: true, force: true });
118
+ return { proc, report };
119
+ }
120
+
121
+ let result = runQuality();
122
+ assert.strictEqual(result.proc.status, 0, result.proc.stderr);
123
+
124
+ result = runQuality(map => { map.layouts[0].runtime_chrome.regions.find(region => region.id === 'canvas').items.length = 1; });
125
+ assert.strictEqual(result.proc.status, 1);
126
+ assert(result.report.errors.some(error => error.includes('three source-backed anatomy items')));
127
+
128
+ result = runQuality(map => { map.layouts[0].runtime_chrome.states[1].context_label = 'Styles'; });
129
+ assert.strictEqual(result.proc.status, 1);
130
+ assert(result.report.errors.some(error => error.includes('generic feature label')));
131
+
132
+ result = runQuality(map => {
133
+ const overlay = map.layouts[0].runtime_chrome.regions.find(region => region.id === 'confirm');
134
+ overlay.items = overlay.items.filter(item => !['templates-group', 'template-change'].includes(item.id));
135
+ });
136
+ assert.strictEqual(result.proc.status, 1);
137
+ assert(result.report.errors.some(error => error.includes('changed-entity group')));
138
+
139
+ result = runQuality(map => { map.route_index.editor.partials_expanded = []; });
140
+ assert.strictEqual(result.proc.status, 1);
141
+ assert(result.report.errors.some(error => error.includes('recursively expanded')));
142
+
143
+ // The reusable WordPress profile converts a shallow detector result into a
144
+ // source-backed schema-v5 recipe that passes both coverage and quality gates.
145
+ {
146
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'rtfm-runtime-profile-'));
147
+ const required = {
148
+ 'src/wp-admin/site-editor.php': "wp.editSite.initializeEditor('site-editor', {});",
149
+ 'src/wp-includes/default-constants.php': "define( 'WP_DEFAULT_THEME', 'twentytwentyfive' );",
150
+ 'src/wp-includes/block-template-utils.php': "'home' => array(\n 'title' => _x( 'Blog Home', 'Template name' ),\n 'description' => __( 'Displays the latest posts.' ),\n),",
151
+ 'src/wp-content/themes/twentytwentyfive/style.css': 'Theme Name: Twenty Twenty-Five\n',
152
+ 'src/wp-content/themes/twentytwentyfive/templates/home.html': '<main>home</main>',
153
+ 'src/wp-content/themes/twentytwentyfive/parts/header.html': '<header/>',
154
+ 'src/wp-content/themes/twentytwentyfive/parts/footer.html': '<footer/>',
155
+ 'src/wp-content/themes/twentytwentyfive/patterns/header.php': 'header',
156
+ 'src/wp-content/themes/twentytwentyfive/patterns/footer.php': 'Designed with WordPress',
157
+ 'src/wp-content/themes/twentytwentyfive/patterns/hidden-blog-heading.php': 'Blog',
158
+ 'src/wp-content/themes/twentytwentyfive/patterns/template-query-loop.php': 'query',
159
+ 'src/wp-content/themes/twentytwentyfive/theme.json': '{}',
160
+ 'src/wp-content/themes/twentytwentyfive/assets/images/botany-flowers.webp': 'image',
161
+ 'src/wp-content/themes/twentytwentyfive/assets/images/category-sunflowers.webp': 'image',
162
+ };
163
+ for (const [rel, value] of Object.entries(required)) write(path.join(root, rel), value);
164
+ const mapPath = path.join(root, 'project_map.json');
165
+ write(mapPath, {
166
+ schema_version: 5, app_shell: { root_classes: { html: ['wp-toolbar'], body: ['wp-admin'] } },
167
+ layouts: [{ name: 'site-editor', path: 'src/wp-admin/site-editor.php', chrome: [], runtime_chrome: {
168
+ source_kind: 'knowledge', source_files: ['src/wp-admin/site-editor.php'], regions: [], states: [],
169
+ }}],
170
+ route_index: { 'site-editor': { path: '/wp-admin/site-editor.php', primary_view: 'src/wp-admin/site-editor.php', layout: 'site-editor' } },
171
+ default_user_assumptions: [], runtime_surface_coverage: { ignored: [] },
172
+ });
173
+ let proc = spawnSync(process.execPath, [profiles, mapPath, root], { encoding: 'utf8' });
174
+ assert.strictEqual(proc.status, 0, proc.stderr);
175
+ const enriched = JSON.parse(fs.readFileSync(mapPath, 'utf8'));
176
+ const site = enriched.layouts.find(layout => layout.name === 'site-editor');
177
+ assert.strictEqual(site.runtime_chrome.states[1].context_label, 'Blog Home');
178
+ assert.strictEqual(site.runtime_chrome.regions.find(region => region.id === 'navigation').appearance.includes('dark #1e1e1e'), true);
179
+ assert.strictEqual(site.runtime_chrome.regions.find(region => region.id === 'confirm').items.filter(item => item.kind === 'selection-row').length, 2);
180
+ proc = spawnSync(process.execPath, [coverage, mapPath, root], { encoding: 'utf8' });
181
+ assert.strictEqual(proc.status, 0, proc.stderr);
182
+ proc = spawnSync(process.execPath, [checker, mapPath, root], { encoding: 'utf8' });
183
+ assert.strictEqual(proc.status, 0, proc.stderr);
184
+ fs.rmSync(root, { recursive: true, force: true });
185
+ }
186
+
187
+ console.log('runtime recipe quality tests passed');