@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,246 @@
1
+ #!/usr/bin/env bash
2
+ # classify_app_type.sh — deterministic app-type classifier for /detect-project
3
+ # when the user gave no app_type hint. Root-level manifests only; conservative
4
+ # by design: it diverts from web only on unambiguous signals. Multi-surface
5
+ # repos (react-native + react-dom, CLI + web framework, monorepos with the app
6
+ # in a subdirectory) default to web and emit a note= line the caller surfaces.
7
+ #
8
+ # Usage: classify_app_type.sh <codebase_dir>
9
+ #
10
+ # Output (line-oriented):
11
+ # type=web|terminal|mobile|desktop|win32|macos|game
12
+ # source=auto|default auto = positive identification; default = nothing matched
13
+ # reason=<one line explaining the decision>
14
+ # note=<0+ lines — ambiguities worth surfacing to the user>
15
+ #
16
+ # Decision order: mobile → desktop (Electron/Tauri — must precede the web
17
+ # signals, since Electron renderers have react-dom) → web signals → game
18
+ # (web/canvas game engine + no web framework — before terminal, a game repo may
19
+ # also carry a CLI helper) → terminal (CLI framework + no web framework; Bevy
20
+ # vetoes the near-universal clap) → win32 (VS project + DIALOGEX .rc, no
21
+ # web/CLI signals) → macos (Xcode project + AppKit menu bar) → monorepo +
22
+ # native-game-engine + vanilla-canvas notes → web.
23
+ #
24
+ # Always exits 0. Bash 3.2 + BSD/GNU grep compatible (no \b, no flock, no jq).
25
+
26
+ CODEBASE="${1:-.}"
27
+ CODEBASE="${CODEBASE%/}"
28
+ NOTES=()
29
+
30
+ has() { [ -e "$CODEBASE/$1" ]; }
31
+ dep() { [ -f "$CODEBASE/$1" ] && grep -Eq "$2" "$CODEBASE/$1" 2>/dev/null; }
32
+
33
+ finish() { # finish <type> <source> <reason>
34
+ printf 'type=%s\nsource=%s\nreason=%s\n' "$1" "$2" "$3"
35
+ for n in ${NOTES[@]+"${NOTES[@]}"}; do printf 'note=%s\n' "$n"; done
36
+ exit 0
37
+ }
38
+
39
+ # ---------- mobile: unambiguous root-level signals ----------
40
+ if dep pubspec.yaml 'sdk:[[:space:]]*flutter'; then
41
+ # Flutter-DESKTOP refinement: desktop platform dirs with no mobile ones means
42
+ # the mobile branch would render this app in a phone frame — wrong, and the
43
+ # desktop branch supports Electron/Tauri only. Note it, stay web.
44
+ if { has macos || has windows || has linux; } && ! has android && ! has ios; then
45
+ NOTES+=("Flutter DESKTOP app (desktop platform dirs, no android/ or ios/) — not supported by the desktop branch (Electron/Tauri only). Defaulting to web; pass app_type=mobile to force phone-frame docs.")
46
+ else
47
+ finish mobile auto "pubspec.yaml declares an sdk: flutter dependency — Flutter app"
48
+ fi
49
+ fi
50
+ if dep package.json '"react-native"[[:space:]]*:'; then
51
+ if dep package.json '"react-dom"[[:space:]]*:'; then
52
+ NOTES+=("package.json has both react-native and react-dom — multi-surface repo (native app + web). Defaulting to web; pass app_type=mobile to document the native app instead.")
53
+ else
54
+ finish mobile auto "package.json depends on react-native with no react-dom — React Native app"
55
+ fi
56
+ fi
57
+
58
+ # ---------- desktop: Electron / Tauri ----------
59
+ # Checked BEFORE the web signals on purpose: an Electron renderer has react-dom
60
+ # (often next/vite too), so the web positive ID would otherwise always win.
61
+ if [ -f "$CODEBASE/src-tauri/tauri.conf.json" ] || [ -f "$CODEBASE/src-tauri/tauri.conf.json5" ] \
62
+ || [ -f "$CODEBASE/src-tauri/Tauri.toml" ] || [ -f "$CODEBASE/tauri.conf.json" ]; then
63
+ finish desktop auto "Tauri configuration found — Tauri desktop app"
64
+ fi
65
+ if dep package.json '"electron"[[:space:]]*:'; then
66
+ # The dep alone is not unambiguous (a web workspace root can list electron for
67
+ # a wrapper subpackage) — require a main-process tell: the package.json "main"
68
+ # entry creating a BrowserWindow, or packaging config (electron-builder/forge/
69
+ # electron-vite).
70
+ ELECTRON_TELL=""
71
+ MAIN_REL=$(sed -n 's/.*"main"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$CODEBASE/package.json" 2>/dev/null | head -1)
72
+ if [ -n "$MAIN_REL" ] && [ -f "$CODEBASE/$MAIN_REL" ] && grep -Eq 'BrowserWindow|app\.whenReady' "$CODEBASE/$MAIN_REL" 2>/dev/null; then
73
+ ELECTRON_TELL="main entry $MAIN_REL creates a BrowserWindow"
74
+ elif [ -f "$CODEBASE/electron-builder.yml" ] || [ -f "$CODEBASE/electron-builder.yaml" ] || [ -f "$CODEBASE/electron-builder.json" ] \
75
+ || [ -f "$CODEBASE/forge.config.js" ] || [ -f "$CODEBASE/forge.config.cjs" ] || [ -f "$CODEBASE/forge.config.ts" ] \
76
+ || [ -f "$CODEBASE/electron.vite.config.js" ] || [ -f "$CODEBASE/electron.vite.config.mjs" ] || [ -f "$CODEBASE/electron.vite.config.ts" ] \
77
+ || dep package.json '"forge"[[:space:]]*:'; then
78
+ ELECTRON_TELL="Electron packaging config present"
79
+ fi
80
+ if [ -n "$ELECTRON_TELL" ]; then
81
+ finish desktop auto "package.json depends on electron and $ELECTRON_TELL — Electron desktop app"
82
+ fi
83
+ NOTES+=("electron dependency found but no main-process tell (compiled entry, or a wrapper subpackage?). Defaulting to web; pass app_type=desktop if this repo is the Electron app.")
84
+ fi
85
+
86
+ # ---------- web framework signals (positive web ID; also the terminal veto) ----------
87
+ WEB_REASON=""
88
+ if has config/routes.rb && has Gemfile; then WEB_REASON="Rails (Gemfile + config/routes.rb)"
89
+ elif dep mix.exs '\{:phoenix'; then WEB_REASON="Phoenix (mix.exs)"
90
+ elif has artisan; then WEB_REASON="Laravel (artisan)"
91
+ elif has manage.py; then WEB_REASON="Django (manage.py)"
92
+ elif has next.config.js || has next.config.mjs || has next.config.ts; then WEB_REASON="Next.js (next.config.*)"
93
+ elif dep package.json '"(react-dom|next|nuxt|vue|svelte|@angular/core|astro|@remix-run/react|express|fastify|koa|hono)"[[:space:]]*:'; then
94
+ WEB_REASON="web framework dependency in package.json"
95
+ elif dep Cargo.toml '^[[:space:]]*(actix-web|axum|rocket|warp|poem)[[:space:]]*='; then
96
+ WEB_REASON="Rust web framework in Cargo.toml"
97
+ elif dep go.mod '(gin-gonic/gin|labstack/echo|gofiber/fiber|go-chi/chi)'; then
98
+ WEB_REASON="Go web framework in go.mod"
99
+ elif dep pyproject.toml '(^(django|flask|fastapi|starlette)[[:space:]]*=|["'"'"'](django|flask|fastapi|starlette)[=>< ~!,"'"'"'])' \
100
+ || dep requirements.txt '^(django|flask|fastapi|starlette)([=><~ !]|$)'; then
101
+ WEB_REASON="Python web framework dependency"
102
+ fi
103
+
104
+ # ---------- game: web/canvas game engine (renders as web tech) ----------
105
+ # Positive only for engines that are games-only in practice. three.js / Babylon /
106
+ # playcanvas are deliberately NOT signals (huge non-game viz/3D usage — no note
107
+ # either). Native engines (Godot/Unity/Unreal/Bevy) are note-only further down:
108
+ # the game branch supports web-rendered games only.
109
+ GAME_REASON=""
110
+ if dep package.json '"(phaser|pixi\.js|@pixi/[a-z-]+|excalibur|kaboom|kaplay|melonjs)"[[:space:]]*:'; then
111
+ GAME_REASON="web game engine dependency in package.json"
112
+ fi
113
+ if [ -n "$GAME_REASON" ]; then
114
+ if [ -z "$WEB_REASON" ]; then
115
+ finish game auto "$GAME_REASON, and no web framework in the root manifests — web/canvas game"
116
+ fi
117
+ NOTES+=("Both a game engine ($GAME_REASON) and a web framework ($WEB_REASON) are present. Defaulting to web; pass app_type=game to document the game instead.")
118
+ fi
119
+
120
+ # ---------- terminal: a CLI/TUI framework AND no web framework at all ----------
121
+ CLI_REASON=""
122
+ if dep Cargo.toml '^[[:space:]]*(clap|structopt)[[:space:]]*='; then
123
+ CLI_REASON="Rust CLI framework (clap/structopt) in Cargo.toml"
124
+ elif dep Cargo.toml '^[[:space:]]*(ratatui|crossterm|cursive|tui)[[:space:]]*='; then
125
+ CLI_REASON="Rust TUI framework in Cargo.toml"
126
+ elif dep go.mod '(spf13/cobra|urfave/cli|charmbracelet/bubbletea)'; then
127
+ CLI_REASON="Go CLI/TUI framework in go.mod"
128
+ elif dep pyproject.toml '^\[project\.scripts\]' || dep setup.cfg 'console_scripts' || dep setup.py 'console_scripts'; then
129
+ CLI_REASON="Python console-script entry points"
130
+ elif dep pyproject.toml '(^(click|typer|textual)[[:space:]]*=|["'"'"'](click|typer|textual)[=>< ~!,"'"'"'])' \
131
+ || dep requirements.txt '^(click|typer|textual)([=><~ !]|$)'; then
132
+ CLI_REASON="Python CLI/TUI framework dependency"
133
+ elif dep package.json '"bin"[[:space:]]*:' && dep package.json '"(commander|yargs|oclif|@oclif/core|ink)"[[:space:]]*:'; then
134
+ CLI_REASON="package.json bin entry + Node CLI framework"
135
+ fi
136
+
137
+ # Bevy veto: a Bevy game near-universally carries clap for launcher args, which
138
+ # would positively classify terminal. Bevy is a native-engine GAME — the mockup
139
+ # pipeline supports web-rendered games only, so veto the CLI signal and note it.
140
+ if dep Cargo.toml '^[[:space:]]*bevy[[:space:]]*='; then
141
+ CLI_REASON=""
142
+ NOTES+=("Cargo.toml carries the Bevy game engine — a native-engine game, not supported by the mockup pipeline (web-rendered games only). Defaulting to web.")
143
+ fi
144
+
145
+ if [ -n "$CLI_REASON" ]; then
146
+ if [ -z "$WEB_REASON" ]; then
147
+ finish terminal auto "$CLI_REASON, and no web framework in the root manifests — CLI/TUI app"
148
+ fi
149
+ NOTES+=("Both a CLI framework ($CLI_REASON) and a web framework ($WEB_REASON) are present. Defaulting to web; pass app_type=terminal to document the CLI instead.")
150
+ fi
151
+
152
+ # ---------- win32: native Windows GUI (Visual Studio project + dialog resources) ----------
153
+ # Unambiguous only when nothing web/CLI matched: a .sln/.vcxproj plus an .rc
154
+ # resource that defines dialogs (DIALOGEX). Qt/.ui apps deliberately do NOT
155
+ # match (unsupported); a C++ CLI has no dialog resources.
156
+ if [ -z "$WEB_REASON" ] && [ -z "$CLI_REASON" ]; then
157
+ VS_PROJ=$(find "$CODEBASE" -maxdepth 3 \( -name '*.vcxproj' -o -name '*.sln' \) -not -path '*/.git/*' 2>/dev/null | head -1)
158
+ if [ -n "$VS_PROJ" ]; then
159
+ RC_DLG=""
160
+ while IFS= read -r rcf; do
161
+ if grep -q 'DIALOGEX' "$rcf" 2>/dev/null; then RC_DLG="$rcf"; break; fi
162
+ done < <(find "$CODEBASE" -maxdepth 4 -name '*.rc' -not -path '*/.git/*' 2>/dev/null | head -20)
163
+ if [ -n "$RC_DLG" ]; then
164
+ finish win32 auto "Visual Studio project + Win32 dialog resources (.rc with DIALOGEX) — native Windows GUI app"
165
+ fi
166
+ fi
167
+ fi
168
+
169
+ # ---------- macos: native AppKit (Xcode project + Interface Builder UI) ----------
170
+ # Checked after mobile/desktop on purpose: Flutter macOS runners and Electron
171
+ # apps also ship .xcodeproj files but are caught by the earlier signals. The
172
+ # gate is <menuItem — an AppKit menu bar is definitive (iOS storyboards have
173
+ # none). Pure-SwiftUI apps (no IB files) get a note, never auto-classified.
174
+ if [ -z "$WEB_REASON" ] && [ -z "$CLI_REASON" ]; then
175
+ XCPROJ=$(find "$CODEBASE" -maxdepth 2 -name '*.xcodeproj' -not -path '*/.git/*' 2>/dev/null | head -1)
176
+ if [ -n "$XCPROJ" ]; then
177
+ IB_FILE=""
178
+ while IFS= read -r xf; do
179
+ if grep -q '<menuItem ' "$xf" 2>/dev/null; then IB_FILE="$xf"; break; fi
180
+ done < <(find "$CODEBASE" -maxdepth 4 \( -name '*.xib' -o -name '*.storyboard' \) -not -path '*/.git/*' 2>/dev/null | head -20)
181
+ if [ -n "$IB_FILE" ]; then
182
+ for fe in qt gtk web android windows linux; do
183
+ if [ -d "$CODEBASE/$fe" ]; then
184
+ NOTES+=("Multi-frontend repo: $fe/ (and possibly others) exists alongside the macOS app. Classifying macos (root Xcode project is the primary-surface signal); pass app_type=web or another hint to document a different surface.")
185
+ break
186
+ fi
187
+ done
188
+ finish macos auto "Xcode project + Interface Builder UI (a .xib/.storyboard defines an AppKit menu bar) — native macOS app"
189
+ fi
190
+ NOTES+=("Xcode project found but no Interface Builder files with an AppKit menu bar (pure SwiftUI, or an iOS app?). Defaulting to web; pass app_type=macos if this is a native macOS app.")
191
+ fi
192
+ fi
193
+
194
+ # ---------- monorepo hints: notes only, never classification ----------
195
+ # An app living in a subdirectory can't be safely classified from the root, but
196
+ # it is worth telling the user where it is (Immich mobile/, LocalSend app/).
197
+ while IFS= read -r f; do
198
+ rel="${f#"$CODEBASE"/}"
199
+ if grep -Eq 'sdk:[[:space:]]*flutter' "$f" 2>/dev/null; then
200
+ NOTES+=("Flutter app found at $rel — to document it, re-run /detect-project with codebase_path=${rel%/pubspec.yaml} app_type=mobile.")
201
+ fi
202
+ done < <(find "$CODEBASE" -mindepth 2 -maxdepth 3 -name pubspec.yaml -not -path '*/node_modules/*' -not -path '*/.git/*' 2>/dev/null | head -3)
203
+
204
+ while IFS= read -r f; do
205
+ rel="${f#"$CODEBASE"/}"
206
+ if grep -Eq '"react-native"[[:space:]]*:' "$f" 2>/dev/null && ! grep -Eq '"react-dom"[[:space:]]*:' "$f" 2>/dev/null; then
207
+ NOTES+=("React Native app found at ${rel%/package.json} — to document it, re-run /detect-project with codebase_path=${rel%/package.json} app_type=mobile.")
208
+ elif grep -Eq '"electron"[[:space:]]*:' "$f" 2>/dev/null; then
209
+ NOTES+=("Electron app found at ${rel%/package.json} — to document it, re-run /detect-project with codebase_path=${rel%/package.json} app_type=desktop.")
210
+ fi
211
+ done < <(find "$CODEBASE" -mindepth 2 -maxdepth 3 -name package.json -not -path '*/node_modules/*' -not -path '*/.git/*' 2>/dev/null | head -20)
212
+
213
+ while IFS= read -r f; do
214
+ rel="${f#"$CODEBASE"/}"
215
+ if grep -Eq '^[[:space:]]*(clap|structopt|ratatui)[[:space:]]*=' "$f" 2>/dev/null; then
216
+ NOTES+=("CLI/TUI crate found at ${rel%/Cargo.toml} (workspace member) — if this repo is a CLI/TUI app, pass app_type=terminal.")
217
+ fi
218
+ done < <(find "$CODEBASE" -mindepth 2 -maxdepth 3 -name Cargo.toml -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/target/*' 2>/dev/null | head -5)
219
+
220
+ # ---------- native game engines: notes only, never classification ----------
221
+ # The game branch supports web-rendered games only; a native-engine project has
222
+ # no web CSS/DOM surface for the pipeline to work with.
223
+ if has project.godot; then
224
+ NOTES+=("project.godot found — a Godot game (native engine), not supported by the mockup pipeline (web-rendered games only).")
225
+ fi
226
+ if [ -f "$CODEBASE/ProjectSettings/ProjectVersion.txt" ]; then
227
+ NOTES+=("ProjectSettings/ProjectVersion.txt found — a Unity game (native engine), not supported by the mockup pipeline (web-rendered games only).")
228
+ fi
229
+ UPROJ=$(find "$CODEBASE" -maxdepth 2 -name '*.uproject' -not -path '*/.git/*' 2>/dev/null | head -1)
230
+ if [ -n "$UPROJ" ]; then
231
+ NOTES+=("${UPROJ#"$CODEBASE"/} found — an Unreal game (native engine), not supported by the mockup pipeline (web-rendered games only).")
232
+ fi
233
+
234
+ # ---------- vanilla-canvas game: note only, adjudicated in STEP 0 ----------
235
+ # A root index.html drawing to a <canvas> with no framework signal at all is
236
+ # often a hand-rolled canvas game — but canvas alone is weak evidence (charts,
237
+ # viz). The note triggers the STEP 0 surface adjudication, which diverts to
238
+ # game only when the repo evidence is one-sided.
239
+ if [ -z "$WEB_REASON" ] && [ -z "$CLI_REASON" ] && [ -z "$GAME_REASON" ] && dep index.html '<canvas'; then
240
+ NOTES+=("Root index.html draws to a <canvas> and no web framework is present — possibly a vanilla-canvas game. If it is, the right type is app_type=game (canvas-scene mockups); the surface adjudication should decide from the repo evidence.")
241
+ fi
242
+
243
+ if [ -n "$WEB_REASON" ]; then
244
+ finish web auto "$WEB_REASON"
245
+ fi
246
+ finish web default "no unambiguous app-type signals in the root manifests — taking the web path"
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env bash
2
+ # classify_surface.sh — does this checkout have a user-facing interface, and of
3
+ # which kind? Wraps classify_app_type.sh (which never says "no UI": an API-only
4
+ # Rails app or an Express JSON server is web to it) with a deterministic
5
+ # no-UI check, so a multi-repo product can tell its surfaces (web app, admin
6
+ # panel, CLI, mobile app) from its code-only repos (APIs, libraries).
7
+ #
8
+ # Usage: classify_surface.sh <codebase_dir>
9
+ #
10
+ # Output (line-oriented):
11
+ # surface=web|terminal|mobile|desktop|win32|macos|game|none
12
+ # source=auto|default auto = positive identification
13
+ # reason=<one line>
14
+ #
15
+ # Only a web verdict is refined: terminal/mobile/desktop/win32/macos/game are
16
+ # positive identifications of an interface. A web verdict becomes
17
+ # - none when Rails declares config.api_only = true;
18
+ # - web when package.json has a frontend framework dependency, or there are
19
+ # UI sources: 3+ template/markup/style/component files outside tests, docs,
20
+ # mailers and vendored/bundled code (engine/, third_party/, assets/vendor/…);
21
+ # - terminal when package.json declares a bin (a hand-rolled Node CLI that
22
+ # classify_app_type.sh's framework list misses);
23
+ # - none otherwise (a server/API or library).
24
+ #
25
+ # Always exits 0. Bash 3.2 + BSD/GNU grep compatible.
26
+
27
+ CODEBASE="${1:-.}"
28
+ CODEBASE="${CODEBASE%/}"
29
+ HERE="$(cd "$(dirname "$0")" && pwd)"
30
+
31
+ finish() { printf 'surface=%s\nsource=%s\nreason=%s\n' "$1" "$2" "$3"; exit 0; }
32
+
33
+ [ -d "$CODEBASE" ] || finish none default "codebase directory does not exist"
34
+
35
+ CLASSIFY=$(bash "$HERE/classify_app_type.sh" "$CODEBASE" 2>/dev/null)
36
+ TYPE=$(printf '%s\n' "$CLASSIFY" | sed -n 's/^type=//p' | head -1)
37
+ SOURCE=$(printf '%s\n' "$CLASSIFY" | sed -n 's/^source=//p' | head -1)
38
+ REASON=$(printf '%s\n' "$CLASSIFY" | sed -n 's/^reason=//p' | head -1)
39
+ TYPE="${TYPE:-web}"
40
+ SOURCE="${SOURCE:-default}"
41
+
42
+ if [ "$TYPE" != "web" ]; then
43
+ finish "$TYPE" "$SOURCE" "$REASON"
44
+ fi
45
+
46
+ # ---------- Rails API mode ----------
47
+ if [ -f "$CODEBASE/config/application.rb" ] &&
48
+ grep -Eq '^[[:space:]]*config\.api_only[[:space:]]*=[[:space:]]*true' "$CODEBASE/config/application.rb" 2>/dev/null; then
49
+ finish none auto "Rails app with config.api_only = true — an API, no user-facing interface"
50
+ fi
51
+
52
+ # ---------- frontend framework dependency ----------
53
+ if [ -f "$CODEBASE/package.json" ] &&
54
+ grep -Eq '"(react-dom|vue|svelte|@sveltejs/kit|@angular/core|solid-js|preact|next|nuxt|@remix-run/react|astro|ember-source|lit|@builder.io/qwik|alpinejs|htmx.org|@hotwired/turbo|@hotwired/stimulus)"[[:space:]]*:' "$CODEBASE/package.json" 2>/dev/null; then
55
+ finish web "$SOURCE" "${REASON:-frontend framework dependency in package.json}"
56
+ fi
57
+
58
+ # ---------- UI sources ----------
59
+ # Templates, markup, styles and component files, excluding places that hold
60
+ # UI-shaped files without being an interface (tests, docs, mail, vendored code,
61
+ # and the skills' own .rtfm cache and ./output mockups).
62
+ UI_COUNT=$(find "$CODEBASE" \
63
+ \( -name .git -o -name node_modules -o -name vendor -o -name dist -o -name build \
64
+ -o -name coverage -o -name tmp -o -name test -o -name tests -o -name spec \
65
+ -o -name __tests__ -o -name fixtures -o -name docs -o -name doc -o -name examples \
66
+ -o -name '*mailer*' -o -name mail -o -name mails -o -name emails -o -name email \
67
+ -o -name .github -o -name target -o -name engine -o -name third_party -o -name third-party \
68
+ -o -name .rtfm -o -name '.rtfm-*' -o -name output \
69
+ -o -path '*/assets/vendor' -o -path '*/public/vendor' -o -path '*/static/vendor' \) -prune -o \
70
+ -type f \( -name '*.html' -o -name '*.htm' -o -name '*.erb' -o -name '*.haml' -o -name '*.slim' \
71
+ -o -name '*.heex' -o -name '*.leex' -o -name '*.eex' -o -name '*.vue' -o -name '*.svelte' \
72
+ -o -name '*.jsx' -o -name '*.tsx' -o -name '*.hbs' -o -name '*.handlebars' -o -name '*.ejs' \
73
+ -o -name '*.pug' -o -name '*.twig' -o -name '*.blade.php' -o -name '*.jinja' -o -name '*.jinja2' \
74
+ -o -name '*.j2' -o -name '*.cshtml' -o -name '*.razor' -o -name '*.liquid' -o -name '*.mustache' \
75
+ -o -name '*.css' -o -name '*.scss' -o -name '*.sass' -o -name '*.less' -o -name '*.templ' \) \
76
+ -print 2>/dev/null | head -50 | wc -l | tr -d ' ')
77
+
78
+ if [ "${UI_COUNT:-0}" -ge 3 ]; then
79
+ finish web "$SOURCE" "${REASON:-UI sources present} (${UI_COUNT}+ template/markup/style files)"
80
+ fi
81
+
82
+ # ---------- Node CLI: a package.json bin with no frontend framework ----------
83
+ # classify_app_type.sh only recognises specific CLI frameworks (commander,
84
+ # yargs, oclif, ink); a hand-rolled Node CLI (process.argv, @clack/prompts, an
85
+ # MCP stdio server) has none of them. A declared executable with no frontend
86
+ # framework and no UI sources of its own (vendored engines/templates are
87
+ # pruned above) is a terminal tool.
88
+ if [ -f "$CODEBASE/package.json" ] && grep -Eq '"bin"[[:space:]]*:' "$CODEBASE/package.json" 2>/dev/null; then
89
+ finish terminal auto "package.json declares a bin (a command-line executable) with no frontend framework dependency"
90
+ fi
91
+
92
+ if [ "$SOURCE" = "auto" ]; then
93
+ finish none auto "web framework but no templates, styles or frontend framework — a server/API with no user-facing interface ($REASON)"
94
+ fi
95
+ finish none default "no interface signals: no app-type manifest, templates, styles or frontend framework — a library or service"
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * classify_workspace.js — run classify_surface.sh on every git checkout
4
+ * directly under a multi-repo workspace root and print JSON:
5
+ *
6
+ * [{ "directory": "acme-web", "surface": "web", "source": "auto", "reason": "…" }, …]
7
+ *
8
+ * Usage: node classify_workspace.js <repos_root>
9
+ * Sorted by directory; prints [] for a missing root. Always exits 0.
10
+ */
11
+ 'use strict';
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const { execFileSync } = require('child_process');
15
+
16
+ const SCRIPT = path.join(__dirname, 'classify_surface.sh');
17
+
18
+ function classifyWorkspace(root) {
19
+ if (!root || !fs.existsSync(root)) return [];
20
+ return fs.readdirSync(root, { withFileTypes: true })
21
+ .filter(entry => entry.isDirectory() && fs.existsSync(path.join(root, entry.name, '.git')))
22
+ .map(entry => entry.name)
23
+ .sort()
24
+ .map(directory => {
25
+ let out = '';
26
+ try { out = execFileSync('bash', [SCRIPT, path.join(root, directory)], { encoding: 'utf8' }); } catch { /* none below */ }
27
+ const fields = Object.fromEntries(out.trim().split('\n').filter(Boolean).map(line => {
28
+ const i = line.indexOf('=');
29
+ return [line.slice(0, i), line.slice(i + 1)];
30
+ }));
31
+ return { directory, surface: fields.surface || 'none', source: fields.source || 'default', reason: fields.reason || '' };
32
+ });
33
+ }
34
+
35
+ if (require.main === module) {
36
+ process.stdout.write(JSON.stringify(classifyWorkspace(process.argv[2]), null, 2) + '\n');
37
+ }
38
+
39
+ module.exports = { classifyWorkspace };