@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,142 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /*
5
+ * json_get.js — read values from a JSON file for shell scripts and SKILL.md
6
+ * steps, so the skills need Node only (no jq).
7
+ *
8
+ * A path is dot-separated keys; a segment ending in [] iterates an array or
9
+ * object's values ("reasons[].code"). "." is the whole document. Like jq's
10
+ * `//`, null, false and missing values fall back to the default.
11
+ *
12
+ * Usage:
13
+ * node json_get.js <file> <path> [default] print a value (arrays: one item per line)
14
+ * node json_get.js --length <file> <path> print the length (0 when missing)
15
+ * node json_get.js --join <sep> <file> <path> join the values with <sep>
16
+ * node json_get.js --count <file> <path> <key>=<value>
17
+ * count values whose <key> equals <value>
18
+ * node json_get.js --exists <file> <path> exit 0 when present and not null/false
19
+ * node json_get.js --valid <file>... exit 0 when every file parses as JSON
20
+ *
21
+ * A missing path with no default prints "null", as jq -r does.
22
+ */
23
+
24
+ const fs = require('fs');
25
+
26
+ function readJson(file) {
27
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
28
+ }
29
+
30
+ /** Resolve a path to its list of values (more than one when a segment iterates). */
31
+ function resolve(doc, path) {
32
+ let values = [doc];
33
+ if (path === '.' || path === '') return values;
34
+ for (const raw of path.replace(/^\./, '').split('.')) {
35
+ const iterate = raw.endsWith('[]');
36
+ const key = iterate ? raw.slice(0, -2) : raw;
37
+ const next = [];
38
+ for (const value of values) {
39
+ const child = key === '' ? value : lookup(value, key);
40
+ if (!iterate) next.push(child);
41
+ else if (Array.isArray(child)) next.push(...child);
42
+ else if (child !== null && typeof child === 'object') next.push(...Object.values(child));
43
+ }
44
+ values = next;
45
+ }
46
+ return values;
47
+ }
48
+
49
+ function lookup(value, key) {
50
+ if (Array.isArray(value) && /^\d+$/.test(key)) return value[Number(key)];
51
+ if (value !== null && typeof value === 'object' && Object.hasOwn(value, key)) return value[key];
52
+ return undefined;
53
+ }
54
+
55
+ function present(value) {
56
+ return value !== undefined && value !== null && value !== false;
57
+ }
58
+
59
+ function format(value) {
60
+ if (value === undefined || value === null) return 'null';
61
+ if (typeof value === 'string') return value;
62
+ if (typeof value === 'object') return JSON.stringify(value, null, 2);
63
+ return String(value);
64
+ }
65
+
66
+ function lengthOf(value) {
67
+ if (Array.isArray(value) || typeof value === 'string') return value.length;
68
+ if (value !== null && typeof value === 'object') return Object.keys(value).length;
69
+ return 0;
70
+ }
71
+
72
+ function main(argv) {
73
+ const mode = argv[0]?.startsWith('--') ? argv.shift() : '--get';
74
+ const out = text => process.stdout.write(text + '\n');
75
+
76
+ switch (mode) {
77
+ case '--get': {
78
+ const [file, path, fallback] = argv;
79
+ if (!file || !path) usage();
80
+ const values = resolve(readJson(file), path).filter(present);
81
+ if (values.length) {
82
+ const [only] = values;
83
+ out(values.length === 1 && Array.isArray(only) ? only.map(format).join('\n') : values.map(format).join('\n'));
84
+ } else {
85
+ out(fallback ?? 'null');
86
+ }
87
+ return 0;
88
+ }
89
+ case '--length': {
90
+ const [file, path] = argv;
91
+ if (!file || !path) usage();
92
+ out(String(lengthOf(resolve(readJson(file), path)[0])));
93
+ return 0;
94
+ }
95
+ case '--join': {
96
+ const [sep, file, path] = argv;
97
+ if (sep === undefined || !file || !path) usage();
98
+ out(resolve(readJson(file), path).filter(present).map(format).join(sep));
99
+ return 0;
100
+ }
101
+ case '--count': {
102
+ const [file, path, condition] = argv;
103
+ if (!file || !path || !condition?.includes('=')) usage();
104
+ const [key, expected] = [condition.slice(0, condition.indexOf('=')), condition.slice(condition.indexOf('=') + 1)];
105
+ const matches = resolve(readJson(file), path).filter(value => format(lookup(value, key)) === expected);
106
+ out(String(matches.length));
107
+ return 0;
108
+ }
109
+ case '--exists': {
110
+ const [file, path] = argv;
111
+ if (!file || !path) usage();
112
+ try { return resolve(readJson(file), path).some(present) ? 0 : 1; }
113
+ catch { return 1; }
114
+ }
115
+ case '--valid': {
116
+ if (!argv.length) usage();
117
+ let status = 0;
118
+ for (const file of argv) {
119
+ try { readJson(file); }
120
+ catch (error) { process.stderr.write(`${file}: ${error.message}\n`); status = 1; }
121
+ }
122
+ return status;
123
+ }
124
+ default:
125
+ usage();
126
+ }
127
+ }
128
+
129
+ function usage() {
130
+ process.stderr.write('Usage: json_get.js [--length|--join <sep>|--count|--exists|--valid] <file> <path> [default]\n');
131
+ process.exit(2);
132
+ }
133
+
134
+ if (require.main === module) {
135
+ try { process.exitCode = main(process.argv.slice(2)); }
136
+ catch (error) {
137
+ process.stderr.write(`json_get: ${error.message}\n`);
138
+ process.exitCode = 1;
139
+ }
140
+ }
141
+
142
+ module.exports = { resolve, lengthOf };
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /*
5
+ * merge_json.js — deep-merge a JSON patch into a JSON file in place.
6
+ *
7
+ * Objects merge key by key; arrays and scalars in the patch replace the
8
+ * target's value. Keys absent from the patch are kept, so stamped fields such
9
+ * as app_type/app_type_source survive an enrichment merge. A null in the patch
10
+ * sets the key to null (it does not delete it).
11
+ *
12
+ * Usage:
13
+ * node merge_json.js <target.json> <patch.json>
14
+ * node merge_json.js <target.json> - (patch on stdin)
15
+ */
16
+
17
+ const fs = require('fs');
18
+
19
+ function isObject(value) {
20
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
21
+ }
22
+
23
+ function merge(target, patch) {
24
+ if (!isObject(target) || !isObject(patch)) return patch;
25
+ const result = { ...target };
26
+ for (const [key, value] of Object.entries(patch)) {
27
+ result[key] = isObject(value) && isObject(result[key]) ? merge(result[key], value) : value;
28
+ }
29
+ return result;
30
+ }
31
+
32
+ function main(argv) {
33
+ const [targetFile, patchFile] = argv;
34
+ if (!targetFile || !patchFile) {
35
+ process.stderr.write('Usage: merge_json.js <target.json> <patch.json|->\n');
36
+ return 2;
37
+ }
38
+ const target = JSON.parse(fs.readFileSync(targetFile, 'utf8'));
39
+ const patch = JSON.parse(fs.readFileSync(patchFile === '-' ? 0 : patchFile, 'utf8'));
40
+ fs.writeFileSync(targetFile, JSON.stringify(merge(target, patch), null, 2) + '\n');
41
+ return 0;
42
+ }
43
+
44
+ if (require.main === module) {
45
+ try { process.exitCode = main(process.argv.slice(2)); }
46
+ catch (error) {
47
+ process.stderr.write(`merge_json: ${error.message}\n`);
48
+ process.exitCode = 1;
49
+ }
50
+ }
51
+
52
+ module.exports = { merge };
@@ -0,0 +1,252 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /*
5
+ * recommend_model_tier.js — classify the model capability needed for mockups.
6
+ *
7
+ * This is deliberately a cache-only postprocessor. detect-project has already
8
+ * paid the cost of resolving routes, chrome, runtime editors, and CSS health;
9
+ * repeating that exploration in another skill would waste both time and model
10
+ * tokens. The parent application maps low/medium/high to current model IDs.
11
+ *
12
+ * Usage:
13
+ * node recommend_model_tier.js <project_map.json> <branding.json>
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+
19
+ const POLICY_VERSION = 'heuristic-v1';
20
+ const VALID_APP_TYPES = new Set(['web', 'terminal', 'mobile', 'desktop', 'win32', 'macos', 'game']);
21
+ const SPECIALIZED_APP_TYPES = new Set(['terminal', 'mobile', 'desktop', 'win32', 'macos']);
22
+ const SHELL_REGION_KEYS = [
23
+ 'top_bar', 'titlebar', 'menu_bar', 'global_sidebar', 'primary_sidebar',
24
+ 'secondary_sidebar', 'statusbar', 'toolbar', 'account_area',
25
+ ];
26
+ const INDEX_KEYS = ['route_index', 'screen_index', 'command_index', 'dialog_index', 'view_index'];
27
+
28
+ function isObject(value) {
29
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
30
+ }
31
+
32
+ function objectValues(value) {
33
+ return isObject(value) ? Object.values(value) : [];
34
+ }
35
+
36
+ function indexCount(map) {
37
+ return Math.max(0, ...INDEX_KEYS.map(key => objectValues(map[key]).length));
38
+ }
39
+
40
+ function shellMetrics(shell) {
41
+ if (!isObject(shell)) return { navItems: 0, persistentRegions: 0 };
42
+ const navItems = Array.isArray(shell.nav_items) ? shell.nav_items.length : 0;
43
+ const persistentRegions = SHELL_REGION_KEYS.filter(key => {
44
+ const value = shell[key];
45
+ if (Array.isArray(value)) return value.length > 0;
46
+ if (isObject(value)) return Object.keys(value).length > 0;
47
+ return typeof value === 'string' ? value.trim().length > 0 : Boolean(value);
48
+ }).length;
49
+ return { navItems, persistentRegions };
50
+ }
51
+
52
+ function maxSourceFanout(map) {
53
+ let max = 0;
54
+ for (const key of INDEX_KEYS) {
55
+ for (const entry of objectValues(map[key])) {
56
+ if (!isObject(entry)) continue;
57
+ const expanded = Array.isArray(entry.partials_expanded) ? entry.partials_expanded.length : 0;
58
+ max = Math.max(max, expanded);
59
+ }
60
+ }
61
+ return max;
62
+ }
63
+
64
+ function runtimeMetrics(map) {
65
+ const layouts = Array.isArray(map.layouts) ? map.layouts : [];
66
+ const recipes = layouts
67
+ .map(layout => isObject(layout) ? layout.runtime_chrome : null)
68
+ .filter(isObject);
69
+ let maxRegions = 0;
70
+ let maxStates = 0;
71
+ let populatedCanvas = false;
72
+ for (const recipe of recipes) {
73
+ const regions = Array.isArray(recipe.regions) ? recipe.regions : [];
74
+ const states = Array.isArray(recipe.states) ? recipe.states : [];
75
+ maxRegions = Math.max(maxRegions, regions.length);
76
+ maxStates = Math.max(maxStates, states.length);
77
+ populatedCanvas ||= regions.some(region => isObject(region)
78
+ && region.placement === 'canvas'
79
+ && (region.content_mode === 'populated' || region.media_expectation === 'required'));
80
+ }
81
+ return { count: recipes.length, maxRegions, maxStates, populatedCanvas };
82
+ }
83
+
84
+ function hasCanvasSceneAnatomy(map, appType) {
85
+ if (appType !== 'game') return false;
86
+ return objectValues(map.route_index).some(entry => isObject(entry)
87
+ && entry.method === 'CANVAS'
88
+ && isObject(entry.scene_anatomy)
89
+ && Array.isArray(entry.scene_anatomy.layers)
90
+ && entry.scene_anatomy.layers.length > 0);
91
+ }
92
+
93
+ function normalizedFramework(map, branding) {
94
+ const raw = map.framework || branding.framework || '';
95
+ return String(raw).trim().toLowerCase();
96
+ }
97
+
98
+ function isKnownWebFramework(framework) {
99
+ return Boolean(framework) && !['custom', 'unknown', 'none', '?'].includes(framework);
100
+ }
101
+
102
+ function classify(projectMap, branding = {}) {
103
+ if (!isObject(projectMap)) throw new Error('project map must be a JSON object');
104
+ if (!isObject(branding)) throw new Error('branding cache must be a JSON object');
105
+
106
+ const framework = normalizedFramework(projectMap, branding);
107
+ const rawAppType = typeof projectMap.app_type === 'string'
108
+ ? projectMap.app_type.trim().toLowerCase() : '';
109
+ // Older web caches predate app_type. Preserve their useful classification
110
+ // when a known web framework makes the intended surface unambiguous.
111
+ const appType = rawAppType || (isKnownWebFramework(framework) ? 'web' : '');
112
+ const layouts = Array.isArray(projectMap.layouts) ? projectMap.layouts.length : 0;
113
+ const surfaces = indexCount(projectMap);
114
+ const shell = shellMetrics(projectMap.app_shell);
115
+ const sourceFanout = maxSourceFanout(projectMap);
116
+ const runtime = runtimeMetrics(projectMap);
117
+ const fallbackCss = branding.compilation_method === 'fallback_synthesis'
118
+ || (isObject(projectMap.css_build) && projectMap.css_build.method === 'fallback_synthesis');
119
+
120
+ let score = 0;
121
+ let hardHigh = false;
122
+ let mediumFloor = false;
123
+ const reasons = [];
124
+ const add = (code, weight, evidence, options = {}) => {
125
+ score += weight;
126
+ reasons.push({ code, weight, evidence });
127
+ hardHigh ||= Boolean(options.hardHigh);
128
+ mediumFloor ||= Boolean(options.mediumFloor);
129
+ };
130
+
131
+ const complexRuntime = runtime.count > 0
132
+ && (runtime.maxRegions >= 4 || runtime.maxStates >= 3 || runtime.populatedCanvas);
133
+ if (complexRuntime) {
134
+ add('complex_runtime_surface', 7, {
135
+ recipe_count: runtime.count,
136
+ max_regions: runtime.maxRegions,
137
+ max_states: runtime.maxStates,
138
+ populated_canvas: runtime.populatedCanvas,
139
+ }, { hardHigh: true });
140
+ } else if (runtime.count > 0) {
141
+ add('runtime_mounted_ui', 3, { recipe_count: runtime.count }, { mediumFloor: true });
142
+ }
143
+
144
+ if (hasCanvasSceneAnatomy(projectMap, appType)) {
145
+ add('canvas_scene_anatomy', 7, { app_type: appType }, { hardHigh: true });
146
+ }
147
+
148
+ const richDesktop = appType === 'desktop'
149
+ && surfaces >= 8
150
+ && shell.persistentRegions >= 3;
151
+ if (richDesktop) {
152
+ add('rich_desktop_workspace', 7, {
153
+ surface_count: surfaces,
154
+ persistent_shell_regions: shell.persistentRegions,
155
+ }, { hardHigh: true });
156
+ } else if (SPECIALIZED_APP_TYPES.has(appType)) {
157
+ add('specialized_app_surface', 2, { app_type: appType }, { mediumFloor: true });
158
+ }
159
+
160
+ if (fallbackCss) {
161
+ add('fallback_css_synthesis', 2, {
162
+ compilation_method: 'fallback_synthesis',
163
+ }, { mediumFloor: true });
164
+ }
165
+
166
+ if (appType === 'web' && !isKnownWebFramework(framework)) {
167
+ add('custom_or_unknown_web_framework', 2, {
168
+ framework: framework || null,
169
+ }, { mediumFloor: true });
170
+ }
171
+
172
+ if (!VALID_APP_TYPES.has(appType)) {
173
+ add('unknown_project_surface', 3, {
174
+ app_type: rawAppType || null,
175
+ framework: framework || null,
176
+ }, { mediumFloor: true });
177
+ }
178
+
179
+ if (sourceFanout >= 8) {
180
+ add('deep_source_fanout', 2, { max_partials_expanded: sourceFanout });
181
+ }
182
+ if (layouts >= 5) add('multiple_layouts', 1, { layout_count: layouts });
183
+ if (surfaces >= 25) add('many_user_surfaces', 1, { surface_count: surfaces });
184
+ if (shell.navItems >= 8 || shell.persistentRegions >= 3) {
185
+ add('complex_persistent_shell', 1, {
186
+ nav_item_count: shell.navItems,
187
+ persistent_region_count: shell.persistentRegions,
188
+ });
189
+ }
190
+
191
+ let tier = score >= 7 ? 'high' : score >= 3 ? 'medium' : 'low';
192
+ if (hardHigh) tier = 'high';
193
+ else if (mediumFloor && tier === 'low') tier = 'medium';
194
+
195
+ if (reasons.length === 0) {
196
+ reasons.push({
197
+ code: 'straightforward_project_structure',
198
+ weight: 0,
199
+ evidence: { app_type: appType || null, framework: framework || null },
200
+ });
201
+ }
202
+
203
+ return {
204
+ schema_version: 1,
205
+ policy_version: POLICY_VERSION,
206
+ tier,
207
+ score,
208
+ source: 'heuristic',
209
+ reasons,
210
+ };
211
+ }
212
+
213
+ function readJson(file, label) {
214
+ try {
215
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
216
+ } catch (error) {
217
+ throw new Error(`cannot read ${label} '${file}': ${error.message}`);
218
+ }
219
+ }
220
+
221
+ function writeJsonAtomic(file, value) {
222
+ const temp = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.tmp`);
223
+ fs.writeFileSync(temp, `${JSON.stringify(value, null, 2)}\n`);
224
+ fs.renameSync(temp, file);
225
+ }
226
+
227
+ function main() {
228
+ const [mapArg, brandingArg] = process.argv.slice(2);
229
+ if (!mapArg || !brandingArg) {
230
+ console.error('Usage: node recommend_model_tier.js <project_map.json> <branding.json>');
231
+ process.exit(1);
232
+ }
233
+ try {
234
+ const mapPath = path.resolve(mapArg);
235
+ const brandingPath = path.resolve(brandingArg);
236
+ const map = readJson(mapPath, 'project map');
237
+ const branding = readJson(brandingPath, 'branding cache');
238
+ map.mockup_model_recommendation = classify(map, branding);
239
+ writeJsonAtomic(mapPath, map);
240
+ const recommendation = map.mockup_model_recommendation;
241
+ const reasonCodes = recommendation.reasons.map(reason => reason.code).join(', ');
242
+ console.log(`mockup model tier: ${recommendation.tier} `
243
+ + `(score ${recommendation.score}; ${reasonCodes})`);
244
+ } catch (error) {
245
+ console.error(`ERROR: ${error.message}`);
246
+ process.exit(1);
247
+ }
248
+ }
249
+
250
+ if (require.main === module) main();
251
+
252
+ module.exports = { classify, POLICY_VERSION };
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * resolve_route_chains.js — fill route_index[*].render_chain deterministically.
4
+ *
5
+ * Why: on component-tree apps (React/Svelte/Next monorepos) the screen behind a
6
+ * route is 30–50 files deep, and the route index the model writes in STEP 4.6
7
+ * records only the primary view. Every article run then rediscovers the chain
8
+ * by grepping the repo (twenty: 270 bash reads, 16 min to the first image).
9
+ * The chain is static per commit, so it belongs in the cache.
10
+ *
11
+ * What: for every route_index entry with a resolvable `primary_view`, walk the
12
+ * include tree with the article skill's include census (shared module —
13
+ * ERB/ViewComponent, JSX/Vue/Svelte, Blade/Livewire, Django/Jinja/Twig, Razor,
14
+ * Phoenix/Angular, PHP requires) recursively, and write
15
+ *
16
+ * render_chain: [{file, via, depth, kind}] kind ∈ unconditional|overlay|conditional
17
+ * render_chain_stats: {files, unresolved, max_depth, truncated}
18
+ *
19
+ * plus `partials_expanded` when the entry has none (the unconditional files, in
20
+ * discovery order — the field the article skill already consumes). Existing
21
+ * `partials_expanded` values are never overwritten. Unresolvable references
22
+ * (bare package imports, dynamic includes) are counted, not invented.
23
+ *
24
+ * Usage: resolve_route_chains.js <project_map.json> <project_dir> [--max-files N] [--max-depth N] [--json <report>]
25
+ * Exit 0 always (a cache enrichment must never fail a detect); prints a summary.
26
+ */
27
+ 'use strict';
28
+
29
+ const fs = require('fs');
30
+ const path = require('path');
31
+ const census = require('./include_census.js');
32
+
33
+ const MAX_FILES = 80;
34
+ const MAX_DEPTH = 6;
35
+ const SKIP_EXT_RE = /\.(?:json|ya?ml|arb|po|css|scss|sass|less|svg|png|jpe?g|gif|webp|ico|woff2?|ttf|md|txt|lock|d\.ts)$/i;
36
+ const SKIP_PATH_RE = /(?:^|\/)(?:node_modules|vendor|dist|build|\.next|\.svelte-kit|test|tests|__tests__|spec|stories?)\//;
37
+
38
+ function resolveChain(root, primaryView, maxFiles, maxDepth) {
39
+ const chain = [];
40
+ const seen = new Set([primaryView]);
41
+ let unresolved = 0, truncated = false, maxSeen = 0;
42
+ // BFS so the shallow, always-rendered pieces come first and a cap cuts the deep tail.
43
+ const queue = [{ file: primaryView, depth: 0, inherited: 'unconditional' }];
44
+ while (queue.length) {
45
+ const { file, depth, inherited } = queue.shift();
46
+ if (depth >= maxDepth) continue;
47
+ let refs;
48
+ try { refs = census.includeRefs(root, file); } catch { continue; }
49
+ for (const ref of refs) {
50
+ if (!ref.file) { unresolved++; continue; }
51
+ if (SKIP_EXT_RE.test(ref.file) || SKIP_PATH_RE.test(ref.file)) continue;
52
+ if (seen.has(ref.file)) continue;
53
+ if (chain.length >= maxFiles) { truncated = true; break; }
54
+ seen.add(ref.file);
55
+ // A piece inherits its parent's condition: an unconditional include of a
56
+ // conditional include is conditional for the screen.
57
+ let kind = ref.overlay ? 'overlay' : (ref.guarded ? 'conditional' : 'unconditional');
58
+ if (inherited === 'overlay' && kind === 'unconditional') kind = 'overlay';
59
+ if (inherited === 'conditional' && kind === 'unconditional') kind = 'conditional';
60
+ chain.push({ file: ref.file, via: file, depth: depth + 1, kind });
61
+ maxSeen = Math.max(maxSeen, depth + 1);
62
+ queue.push({ file: ref.file, depth: depth + 1, inherited: kind });
63
+ }
64
+ if (truncated) break;
65
+ }
66
+ return { chain, stats: { files: chain.length, unresolved, max_depth: maxSeen, truncated } };
67
+ }
68
+
69
+ function main() {
70
+ const args = process.argv.slice(2);
71
+ const positional = args.filter(a => !a.startsWith('--'));
72
+ const opt = (name, dflt) => { const i = args.indexOf(name); return i >= 0 && args[i + 1] ? args[i + 1] : dflt; };
73
+ const mapPath = positional[0];
74
+ const root = path.resolve(positional[1] || process.cwd());
75
+ const maxFiles = parseInt(opt('--max-files', MAX_FILES), 10) || MAX_FILES;
76
+ const maxDepth = parseInt(opt('--max-depth', MAX_DEPTH), 10) || MAX_DEPTH;
77
+ const reportPath = opt('--json', null);
78
+ if (!mapPath || !fs.existsSync(mapPath)) {
79
+ console.log('resolve_route_chains: usage: resolve_route_chains.js <project_map.json> <project_dir> [--max-files N] [--max-depth N] [--json report]');
80
+ return 0;
81
+ }
82
+ let pm;
83
+ try { pm = JSON.parse(fs.readFileSync(mapPath, 'utf8')); } catch (e) {
84
+ console.log(`resolve_route_chains: project_map.json unreadable (${e.message}) — nothing changed`);
85
+ return 0;
86
+ }
87
+ const ri = pm.route_index;
88
+ if (!ri || typeof ri !== 'object' || Array.isArray(ri)) {
89
+ console.log('resolve_route_chains: no route_index object — nothing to resolve');
90
+ return 0;
91
+ }
92
+ const report = { resolved: 0, skipped: 0, total_files: 0, routes: {}, layouts: {} };
93
+ const readable = p => { try { return !!p && fs.statSync(path.join(root, p)).isFile(); } catch { return false; } };
94
+ for (const [key, entry] of Object.entries(ri)) {
95
+ if (!entry || typeof entry !== 'object') { report.skipped++; continue; }
96
+ let pv = typeof entry.primary_view === 'string' ? entry.primary_view.replace(/^\/+/, '') : null;
97
+ if (!pv && typeof entry.controller === 'string' && /^[\w/]+#\w+$/.test(entry.controller)) {
98
+ // Rails convention: `things#index` renders app/views/things/index.html.erb
99
+ // unless the action says otherwise; recorded as the derivation it is.
100
+ const [ctrl, action] = entry.controller.split('#');
101
+ for (const ext of ['.html.erb', '.html.haml', '.html.slim']) {
102
+ const cand = `app/views/${ctrl}/${action}${ext}`;
103
+ if (readable(cand)) { pv = cand; entry.primary_view = cand; entry.primary_view_source = 'convention'; break; }
104
+ }
105
+ }
106
+ if (!readable(pv)) { report.skipped++; continue; }
107
+ const { chain, stats } = resolveChain(root, pv, maxFiles, maxDepth);
108
+ entry.render_chain = chain;
109
+ entry.render_chain_stats = stats;
110
+ if (!Array.isArray(entry.partials_expanded) || !entry.partials_expanded.length) {
111
+ entry.partials_expanded = chain.filter(c => c.kind === 'unconditional').map(c => c.file);
112
+ }
113
+ report.routes[key] = { primary_view: pv, ...stats };
114
+ report.resolved++; report.total_files += stats.files;
115
+ }
116
+ // The recorded layouts are the shell: resolve their chains once too, so a run
117
+ // opens the shell's real files instead of re-deriving them from `chrome`.
118
+ if (Array.isArray(pm.layouts)) for (const lay of pm.layouts) {
119
+ const lp = lay && typeof lay.path === 'string' ? lay.path.replace(/^\/+/, '') : null;
120
+ if (!readable(lp)) continue;
121
+ const { chain, stats } = resolveChain(root, lp, maxFiles, maxDepth);
122
+ lay.render_chain = chain; lay.render_chain_stats = stats;
123
+ report.layouts[lay.name || lp] = { path: lp, ...stats };
124
+ }
125
+ pm.route_chain_resolver = { version: 1, generated_at: new Date().toISOString(), max_files: maxFiles, max_depth: maxDepth };
126
+ fs.writeFileSync(mapPath, JSON.stringify(pm, null, 2) + '\n');
127
+ if (reportPath) fs.writeFileSync(reportPath, JSON.stringify(report, null, 2) + '\n');
128
+ const deep = Object.entries(report.routes).filter(([, s]) => s.files >= 10).length;
129
+ const layN = Object.keys(report.layouts).length;
130
+ console.log(`resolve_route_chains: ${report.resolved} route(s) resolved (${report.total_files} chain files, ${deep} route(s) with ≥10), ${report.skipped} skipped (no readable primary_view); ${layN} layout chain(s)`);
131
+ return 0;
132
+ }
133
+
134
+ if (require.main === module) process.exitCode = main();
135
+ module.exports = { resolveChain };
@@ -0,0 +1,40 @@
1
+ #!/bin/bash
2
+ # run_css_build.sh — run an LLM-supplied CSS build command with a timeout, then
3
+ # validate the expected output CSS was produced. detect-project STEP 3 uses this to
4
+ # run a project-specific compile it discovered — a Tailwind v4 CLI invocation with
5
+ # `--content` globs, a project's own `mix assets.build` / `npm run build:css`, etc.
6
+ # — without each run reinventing timeout + validation. The mechanics are
7
+ # deterministic; the LLM supplies the command (and templates the output path into it).
8
+ #
9
+ # Usage: run_css_build.sh <project_dir> <output_css> <timeout_secs> -- <command...>
10
+ # The <command...> is expected to write CSS to <output_css> (e.g. `... -o <output_css>`).
11
+ # Exit: 0 if <output_css> exists and is non-trivial (>2KB); 1 otherwise.
12
+
13
+ set -u
14
+
15
+ PROJECT_DIR="$1"; OUTPUT_CSS="$2"; TIMEOUT="${3:-180}"; shift 3
16
+ [ "${1:-}" = "--" ] && shift
17
+
18
+ _timeout() {
19
+ if command -v gtimeout &>/dev/null; then gtimeout "$@";
20
+ elif command -v timeout &>/dev/null; then timeout "$@";
21
+ else shift; "$@"; fi
22
+ }
23
+
24
+ if [ ! -d "$PROJECT_DIR" ]; then echo " run_css_build: project_dir not found: $PROJECT_DIR" >&2; exit 1; fi
25
+ if [ "$#" -eq 0 ]; then echo " run_css_build: no command given" >&2; exit 1; fi
26
+
27
+ echo " run_css_build: (cwd=$PROJECT_DIR, timeout ${TIMEOUT}s) $*" >&2
28
+ ( cd "$PROJECT_DIR" && _timeout "$TIMEOUT" "$@" ) >/tmp/css_build.log 2>&1
29
+ rc=$?
30
+ if [ "$rc" -ne 0 ]; then
31
+ echo " build command exited $rc" >&2
32
+ tail -12 /tmp/css_build.log >&2
33
+ fi
34
+
35
+ if [ -f "$OUTPUT_CSS" ] && [ "$(wc -c < "$OUTPUT_CSS" 2>/dev/null || echo 0)" -gt 2000 ]; then
36
+ echo " build OK: $OUTPUT_CSS ($(wc -c < "$OUTPUT_CSS") bytes)" >&2
37
+ exit 0
38
+ fi
39
+ echo " build produced no usable CSS at $OUTPUT_CSS" >&2
40
+ exit 1