cluaupp 1.5.0 → 1.5.1

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 (323) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +10 -5
  3. package/api/CHANGELOG.md +3 -0
  4. package/api/chat.js +74 -0
  5. package/api/fixtures/roblox/README.md +6 -0
  6. package/api/fixtures/roblox/aux/sample.d.luau +6 -0
  7. package/api/fixtures/roblox/mini/Mini-API-Dump.json +72 -0
  8. package/api/generated/bench-latest.json +39 -0
  9. package/api/generated/capability-profile.json +76 -0
  10. package/api/generated/lsp-index.json +1 -0
  11. package/api/generated/luau/classes.d.luau +11221 -0
  12. package/api/generated/luau/datatypes.d.luau +327 -0
  13. package/api/generated/luau/enums.d.luau +4918 -0
  14. package/api/generated/luau/globals.d.luau +12 -0
  15. package/api/manifests/roblox-target.manifest.json +22 -0
  16. package/api/normalized/roblox-target.profile.json +237461 -0
  17. package/api/overrides/classes.json +8 -0
  18. package/api/overrides/compatibility.json +9 -0
  19. package/api/overrides/datatypes.json +1579 -0
  20. package/api/overrides/enums.json +8 -0
  21. package/api/overrides/globals.json +8 -0
  22. package/api/overrides/type-mappings.json +9 -0
  23. package/api/roblox-api.lock.json +9 -0
  24. package/data/Mini-API-Dump.json +1 -0
  25. package/editors/vscode/README.md +12 -10
  26. package/editors/vscode/extension.js +82 -0
  27. package/editors/vscode/icons/axiom.svg +4 -0
  28. package/editors/vscode/icons/bloom.svg +5 -0
  29. package/editors/vscode/icons/helm.svg +4 -0
  30. package/editors/vscode/icons/hive.svg +5 -0
  31. package/editors/vscode/icons/mint.svg +4 -0
  32. package/editors/vscode/icons/shift.svg +6 -0
  33. package/editors/vscode/language-configuration-schema.json +24 -0
  34. package/editors/vscode/package.json +172 -12
  35. package/editors/vscode/snippets/axiom.json +12 -0
  36. package/editors/vscode/snippets/bloom.json +12 -0
  37. package/editors/vscode/snippets/helm.json +12 -0
  38. package/editors/vscode/snippets/hive.json +12 -0
  39. package/editors/vscode/snippets/mint.json +12 -0
  40. package/editors/vscode/snippets/shift.json +17 -0
  41. package/editors/vscode/syntaxes/axiom.tmLanguage.json +5 -0
  42. package/editors/vscode/syntaxes/bloom.tmLanguage.json +5 -0
  43. package/editors/vscode/syntaxes/helm.tmLanguage.json +5 -0
  44. package/editors/vscode/syntaxes/hive.tmLanguage.json +5 -0
  45. package/editors/vscode/syntaxes/mint.tmLanguage.json +5 -0
  46. package/editors/vscode/syntaxes/schema.tmLanguage.json +89 -0
  47. package/editors/vscode/syntaxes/shift.tmLanguage.json +5 -0
  48. package/examples/game/.cluaupp/compile-cache/ReplicatedStorage__Shared__Constants__Datas__TemplateData.clh.json +1 -0
  49. package/examples/game/.cluaupp/compile-cache/ServerScriptService__Boot__DataBoot.server.clpp.json +1 -0
  50. package/examples/game/.cluaupp/compile-cache/ServerScriptService__Configuration__ServerConfig.clp.json +1 -0
  51. package/examples/game/.cluaupp/compile-cache/ServerScriptService__Handlers__Guard.server.clpp.json +1 -0
  52. package/examples/game/.cluaupp/compile-cache/ServerScriptService__Handlers__PlayerHandler.server.clpp.json +1 -0
  53. package/examples/game/.cluaupp/compile-cache/StarterPlayer__StarterCharacterScripts__Character__CharacterReady.client.clpp.json +1 -0
  54. package/examples/game/.cluaupp/compile-cache/StarterPlayer__StarterPlayerScripts__Controllers__DataController.client.clpp.json +1 -0
  55. package/examples/game/.cluaupp/compile-cache/index.json +12 -0
  56. package/examples/game/.cluaupp/datamodel-profile.json +94 -0
  57. package/examples/game/.vscode/settings.json +12 -1
  58. package/examples/game/include/clpp/datatypes.clh +337 -373
  59. package/examples/game/include/clpp/generated/datamodel.clh +25 -0
  60. package/examples/game/include/clpp/generated/enums.clh +1 -1
  61. package/examples/game/include/clpp/generated/instances.clh +41135 -41064
  62. package/examples/game/include/clpp/libs/axiom.clh +1 -1
  63. package/examples/game/include/clpp/libs/math.clh +14 -0
  64. package/examples/game/include/clpp/libs/spring.clh +17 -0
  65. package/examples/game/include/clpp/libs/ward.clh +1 -0
  66. package/examples/game/include/clpp/roblox.clh +3 -4
  67. package/examples/game/libs/ArrayIndexer/init.luau +209 -0
  68. package/examples/game/libs/Axiom/init.luau +608 -0
  69. package/examples/game/libs/Bloom/init.luau +433 -0
  70. package/examples/game/libs/Coil/init.luau +215 -0
  71. package/examples/game/libs/Crest/init.luau +426 -0
  72. package/examples/game/libs/Echo/init.luau +417 -0
  73. package/examples/game/libs/Ember/init.luau +365 -0
  74. package/examples/game/libs/Flare/init.luau +717 -0
  75. package/examples/game/libs/Gleam/init.luau +729 -0
  76. package/examples/game/libs/Guide/init.luau +215 -0
  77. package/examples/game/libs/Helm/init.luau +377 -0
  78. package/examples/game/libs/Hive/init.luau +241 -0
  79. package/examples/game/libs/Keep/Client.luau +114 -0
  80. package/examples/game/libs/Keep/Data.luau +883 -0
  81. package/examples/game/libs/Keep/Engine.luau +211 -0
  82. package/examples/game/libs/Keep/Enum.luau +6 -0
  83. package/examples/game/libs/Keep/Enums.luau +10 -0
  84. package/examples/game/libs/Keep/Path.luau +236 -0
  85. package/examples/game/libs/Keep/Replicator.luau +298 -0
  86. package/examples/game/libs/Keep/Server.luau +251 -0
  87. package/examples/game/libs/Keep/Store.luau +652 -0
  88. package/examples/game/libs/Keep/Trade.luau +455 -0
  89. package/examples/game/libs/Keep/init.luau +205 -0
  90. package/examples/game/libs/Lens/init.luau +609 -0
  91. package/examples/game/libs/Mint/init.luau +279 -0
  92. package/examples/game/libs/Net/init.luau +156 -0
  93. package/examples/game/libs/Net/src/codec.luau +224 -0
  94. package/examples/game/libs/Occlude/init.luau +117 -0
  95. package/examples/game/libs/Pin/init.luau +309 -0
  96. package/examples/game/libs/Promise/LICENSE +21 -0
  97. package/examples/game/libs/Promise/_impl/LICENSE +21 -0
  98. package/examples/game/libs/Promise/_impl/init.lua +1991 -0
  99. package/examples/game/libs/Promise/init.lua +1990 -0
  100. package/examples/game/libs/Promise/init.luau +13 -0
  101. package/examples/game/libs/Promise/types.luau +46 -0
  102. package/examples/game/libs/Roster/init.luau +686 -0
  103. package/examples/game/libs/SOURCES.md +31 -0
  104. package/examples/game/libs/Shift/init.luau +238 -0
  105. package/examples/game/libs/Spark/init.luau +487 -0
  106. package/examples/game/libs/Stage/init.luau +178 -0
  107. package/examples/game/libs/Sweep/init.luau +259 -0
  108. package/examples/game/libs/Trace/init.luau +213 -0
  109. package/examples/game/libs/Ward/init.luau +211 -0
  110. package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau +29 -0
  111. package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau.map.json +13 -0
  112. package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau +83 -0
  113. package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau.map.json +13 -0
  114. package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau +18 -0
  115. package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau.map.json +13 -0
  116. package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau +14 -0
  117. package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau.map.json +13 -0
  118. package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau +15 -0
  119. package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau.map.json +13 -0
  120. package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau +50 -0
  121. package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau.map.json +13 -0
  122. package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau +12 -0
  123. package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau.map.json +13 -0
  124. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau +16 -0
  125. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau.map.json +13 -0
  126. package/examples/game/src/ReplicatedStorage/Shared/Net/Net.clh +26 -0
  127. package/examples/game/src/ServerScriptService/Boot/DataBoot.server.clpp +1 -1
  128. package/examples/game/src/ServerScriptService/Handlers/PlayerHandler.server.clpp +1 -1
  129. package/examples/game/stylua.toml +9 -0
  130. package/generated/api/build-profile.js +115 -0
  131. package/generated/api/cache.js +44 -0
  132. package/generated/api/coverage.js +32 -0
  133. package/generated/api/diff.js +125 -0
  134. package/generated/api/generators/clpp-headers.js +353 -0
  135. package/generated/api/generators/docs-reference.js +7 -0
  136. package/generated/api/generators/lsp-index.js +81 -0
  137. package/generated/api/generators/luau-defs.js +94 -0
  138. package/generated/api/ide.js +76 -0
  139. package/generated/api/identifier.js +47 -0
  140. package/generated/api/index.js +324 -0
  141. package/generated/api/loader.js +57 -0
  142. package/generated/api/merge.js +88 -0
  143. package/generated/api/model.js +5 -0
  144. package/generated/api/normalize.js +231 -0
  145. package/generated/api/overrides-validate.js +58 -0
  146. package/generated/api/perf.js +12 -0
  147. package/generated/api/registry.js +136 -0
  148. package/generated/api/release.js +44 -0
  149. package/generated/api/resolver.js +250 -0
  150. package/generated/api/sources/aux-types-diff.js +75 -0
  151. package/generated/api/sources/client-tracker.js +11 -0
  152. package/generated/api/sources/creator-docs.js +42 -0
  153. package/generated/api/sources/luau-types.js +34 -0
  154. package/generated/api/sources/mini-dump.js +17 -0
  155. package/generated/api/sources/studio-api.js +8 -0
  156. package/generated/api/sources/studio-apiv2.js +11 -0
  157. package/generated/api/sources/studio-full-api.js +8 -0
  158. package/generated/api/studio-smoke.js +80 -0
  159. package/generated/api/type-parse.js +49 -0
  160. package/generated/api/understander.js +51 -0
  161. package/generated/api/validate.js +135 -0
  162. package/generated/api-cli.js +421 -0
  163. package/generated/cli.js +16 -5
  164. package/generated/clpp/contract.js +3 -3
  165. package/generated/clpp/format-luau.js +236 -0
  166. package/generated/clpp/index.js +13 -1
  167. package/generated/clpp/modules.js +227 -0
  168. package/generated/clpp/postprocess.js +86 -5
  169. package/generated/clpp/rewrite-janitor.js +145 -0
  170. package/generated/clpp/runner.js +14 -2
  171. package/generated/compile.js +82 -0
  172. package/generated/flare/parse.js +12 -1
  173. package/generated/intellisense.js +57 -1
  174. package/generated/native/index.js +217 -16
  175. package/generated/target/authority-check.js +136 -0
  176. package/generated/target/authority-graph.js +74 -0
  177. package/generated/target/build-cache.js +112 -0
  178. package/generated/target/capabilities.js +89 -0
  179. package/generated/target/capability-profile.js +30 -0
  180. package/generated/target/component-contracts.js +60 -0
  181. package/generated/target/context-check.js +160 -0
  182. package/generated/target/datamodel-check.js +170 -0
  183. package/generated/target/datamodel-generate.js +74 -0
  184. package/generated/target/datamodel.js +233 -0
  185. package/generated/target/diagnostics.js +63 -0
  186. package/generated/target/doctor.js +152 -0
  187. package/generated/target/flare-version.js +58 -0
  188. package/generated/target/get-service.js +69 -0
  189. package/generated/target/lifetime-check.js +47 -0
  190. package/generated/target/luau-stamp.js +39 -0
  191. package/generated/target/optimizer-advise.js +88 -0
  192. package/generated/target/optimizer-apply.js +44 -0
  193. package/generated/target/optimizer-soa.js +254 -0
  194. package/generated/target/parallel-check.js +136 -0
  195. package/generated/target/pgo.js +148 -0
  196. package/generated/target/project-docs.js +69 -0
  197. package/generated/target/project-graph.js +71 -0
  198. package/generated/target/roblox.js +63 -0
  199. package/generated/target/security-check.js +41 -0
  200. package/generated/target/source-map.js +25 -0
  201. package/generated/target/studio-bridge.js +112 -0
  202. package/generated/utils/project.js +178 -8
  203. package/generated/utils/rojo-mapper.js +11 -3
  204. package/include/clpp/datatypes.clh +337 -373
  205. package/include/clpp/generated/enums.clh +1 -1
  206. package/include/clpp/generated/instances.clh +41135 -41064
  207. package/include/clpp/libs/axiom.clh +1 -1
  208. package/include/clpp/libs/hive.clh +1 -0
  209. package/include/clpp/libs/keep.clh +2 -0
  210. package/include/clpp/libs/math.clh +14 -0
  211. package/include/clpp/libs/spring.clh +17 -0
  212. package/include/clpp/libs/ward.clh +1 -0
  213. package/include/clpp/roblox.clh +3 -4
  214. package/package.json +10 -3
  215. package/plugins/CluauppNav/Plugin.luau +207 -0
  216. package/plugins/CluauppNav/ProfileLogger.luau +27 -0
  217. package/plugins/CluauppNav/README.md +24 -0
  218. package/runtime/Axiom/init.luau +10 -7
  219. package/runtime/Coil/init.luau +8 -2
  220. package/runtime/Crest/init.luau +1 -0
  221. package/runtime/Flare/init.luau +2 -1
  222. package/runtime/Gleam/init.luau +15 -4
  223. package/runtime/Hive/init.luau +36 -14
  224. package/runtime/Pin/init.luau +5 -2
  225. package/runtime/Roster/init.luau +2 -0
  226. package/runtime/Spark/init.luau +3 -0
  227. package/runtime/Sweep/init.luau +2 -0
  228. package/runtime/Trace/init.luau +3 -0
  229. package/runtime/Ward/init.luau +38 -5
  230. package/scripts/postinstall-editor.js +17 -0
  231. package/skills/README.md +13 -0
  232. package/skills/clpp-hive-flare/SKILL.md +79 -0
  233. package/skills/clpp-language/SKILL.md +123 -0
  234. package/skills/clpp-language/compiler.md +89 -0
  235. package/skills/clpp-style/SKILL.md +95 -0
  236. package/src/api/build-profile.ts +138 -0
  237. package/src/api/cache.ts +45 -0
  238. package/src/api/coverage.ts +46 -0
  239. package/src/api/diff.ts +143 -0
  240. package/src/api/generators/clpp-headers.ts +377 -0
  241. package/src/api/generators/docs-reference.ts +11 -0
  242. package/src/api/generators/lsp-index.ts +106 -0
  243. package/src/api/generators/luau-defs.ts +98 -0
  244. package/src/api/ide.ts +87 -0
  245. package/src/api/identifier.ts +47 -0
  246. package/src/api/index.ts +269 -0
  247. package/src/api/loader.ts +60 -0
  248. package/src/api/merge.ts +126 -0
  249. package/src/api/model.ts +283 -0
  250. package/src/api/normalize.ts +257 -0
  251. package/src/api/overrides-validate.ts +58 -0
  252. package/src/api/perf.ts +15 -0
  253. package/src/api/registry.ts +162 -0
  254. package/src/api/release.ts +47 -0
  255. package/src/api/resolver.ts +255 -0
  256. package/src/api/sources/aux-types-diff.ts +81 -0
  257. package/src/api/sources/client-tracker.ts +10 -0
  258. package/src/api/sources/creator-docs.ts +37 -0
  259. package/src/api/sources/luau-types.ts +30 -0
  260. package/src/api/sources/mini-dump.ts +50 -0
  261. package/src/api/sources/studio-api.ts +7 -0
  262. package/src/api/sources/studio-apiv2.ts +10 -0
  263. package/src/api/sources/studio-full-api.ts +7 -0
  264. package/src/api/studio-smoke.ts +82 -0
  265. package/src/api/type-parse.ts +48 -0
  266. package/src/api/understander.ts +72 -0
  267. package/src/api/validate.ts +160 -0
  268. package/src/api-cli.ts +493 -0
  269. package/src/api.generated.js +37 -22
  270. package/src/cli.ts +21 -6
  271. package/src/clpp/contract.ts +34 -3
  272. package/src/clpp/format-luau.ts +262 -0
  273. package/src/clpp/index.ts +13 -2
  274. package/src/clpp/modules.ts +252 -0
  275. package/src/clpp/postprocess.ts +100 -5
  276. package/src/clpp/rewrite-janitor.ts +156 -0
  277. package/src/clpp/runner.ts +14 -2
  278. package/src/compile.ts +87 -0
  279. package/src/flare/parse.ts +14 -1
  280. package/src/flare/types.ts +2 -0
  281. package/src/intellisense.ts +58 -1
  282. package/src/native/index.ts +225 -16
  283. package/src/target/authority-check.ts +162 -0
  284. package/src/target/authority-graph.ts +80 -0
  285. package/src/target/build-cache.ts +129 -0
  286. package/src/target/capabilities.ts +115 -0
  287. package/src/target/capability-profile.ts +25 -0
  288. package/src/target/component-contracts.ts +64 -0
  289. package/src/target/context-check.ts +177 -0
  290. package/src/target/datamodel-check.ts +188 -0
  291. package/src/target/datamodel-generate.ts +78 -0
  292. package/src/target/datamodel.ts +261 -0
  293. package/src/target/diagnostics.ts +67 -0
  294. package/src/target/doctor.ts +171 -0
  295. package/src/target/flare-version.ts +61 -0
  296. package/src/target/get-service.ts +85 -0
  297. package/src/target/lifetime-check.ts +45 -0
  298. package/src/target/luau-stamp.ts +33 -0
  299. package/src/target/optimizer-advise.ts +93 -0
  300. package/src/target/optimizer-apply.ts +63 -0
  301. package/src/target/optimizer-soa.ts +270 -0
  302. package/src/target/parallel-check.ts +135 -0
  303. package/src/target/pgo.ts +157 -0
  304. package/src/target/project-docs.ts +68 -0
  305. package/src/target/project-graph.ts +75 -0
  306. package/src/target/roblox.ts +78 -0
  307. package/src/target/security-check.ts +40 -0
  308. package/src/target/source-map.ts +36 -0
  309. package/src/target/studio-bridge.ts +140 -0
  310. package/src/types.ts +14 -0
  311. package/src/utils/project.ts +183 -9
  312. package/src/utils/rojo-mapper.ts +12 -3
  313. package/stylua.toml +9 -0
  314. package/templates/game/.vscode/settings.json +7 -1
  315. package/templates/game/include/clpp/datatypes.clh +337 -373
  316. package/templates/game/include/clpp/generated/enums.clh +1 -1
  317. package/templates/game/include/clpp/generated/instances.clh +41135 -41064
  318. package/templates/game/include/clpp/libs/axiom.clh +1 -1
  319. package/templates/game/include/clpp/libs/ward.clh +1 -0
  320. package/templates/game/include/clpp/roblox.clh +3 -4
  321. package/templates/game/src/ServerScriptService/Boot/DataBoot.server.clpp +1 -1
  322. package/templates/game/src/ServerScriptService/Handlers/PlayerHandler.server.clpp +1 -1
  323. package/templates/game/stylua.toml +9 -0
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Profile-Guided Optimization (PGO) — format, load, Studio ingest.
3
+ * cluaupp.profile.json weights optimizer advice toward hot symbols.
4
+ */
5
+
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+ import type { OptimizeAdvice } from "./optimizer-advise.js";
9
+
10
+ export const PGO_SCHEMA_VERSION = 1 as const;
11
+
12
+ export interface CluauppProfile {
13
+ schemaVersion: typeof PGO_SCHEMA_VERSION;
14
+ generatedAt?: string;
15
+ /** Symbol / function name → call counts (or relative weight). */
16
+ hot: Record<string, number>;
17
+ /** Optional struct names observed as hot. */
18
+ structs?: Record<string, number>;
19
+ }
20
+
21
+ /** One Studio / playtest hit — JSONL or JSON array. */
22
+ export interface ProfileEvent {
23
+ symbol: string;
24
+ kind?: "hot" | "struct";
25
+ count?: number;
26
+ }
27
+
28
+ export function defaultProfilePath(projectRoot: string): string {
29
+ return path.join(projectRoot, "cluaupp.profile.json");
30
+ }
31
+
32
+ export function loadProfile(file: string): CluauppProfile | null {
33
+ if (!fs.existsSync(file)) {
34
+ return null;
35
+ }
36
+ try {
37
+ const raw = JSON.parse(fs.readFileSync(file, "utf8")) as CluauppProfile;
38
+ if (!raw || typeof raw.hot !== "object") {
39
+ return null;
40
+ }
41
+ return { schemaVersion: PGO_SCHEMA_VERSION, hot: raw.hot, structs: raw.structs, generatedAt: raw.generatedAt };
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+
47
+ /** Write an empty / template profile for games to fill from Studio logging. */
48
+ export function writeProfileTemplate(projectRoot: string): string {
49
+ const file = defaultProfilePath(projectRoot);
50
+ const template: CluauppProfile = {
51
+ schemaVersion: PGO_SCHEMA_VERSION,
52
+ generatedAt: new Date().toISOString(),
53
+ hot: {
54
+ CalculateDamage: 0,
55
+ EnemyUpdate: 0,
56
+ },
57
+ structs: {
58
+ EnemyState: 0,
59
+ },
60
+ };
61
+ fs.writeFileSync(file, JSON.stringify(template, null, "\t") + "\n", "utf8");
62
+ return file;
63
+ }
64
+
65
+ function parseEvents(raw: string): ProfileEvent[] {
66
+ const trimmed = raw.trim();
67
+ if (!trimmed) return [];
68
+ if (trimmed.startsWith("[")) {
69
+ const arr = JSON.parse(trimmed) as unknown;
70
+ if (!Array.isArray(arr)) return [];
71
+ return arr.filter((e) => e && typeof (e as ProfileEvent).symbol === "string") as ProfileEvent[];
72
+ }
73
+ if (trimmed.startsWith("{") && !trimmed.includes("\n")) {
74
+ const one = JSON.parse(trimmed) as Record<string, unknown>;
75
+ if (typeof one.symbol === "string") {
76
+ return [one as unknown as ProfileEvent];
77
+ }
78
+ if (one.hot && typeof one.hot === "object") {
79
+ return Object.entries(one.hot as Record<string, number>).map(([symbol, count]) => ({
80
+ symbol,
81
+ kind: "hot" as const,
82
+ count: Number(count) || 0,
83
+ }));
84
+ }
85
+ }
86
+ const events: ProfileEvent[] = [];
87
+ for (const line of trimmed.split(/\r?\n/)) {
88
+ const t = line.trim();
89
+ if (!t || t.startsWith("#")) continue;
90
+ try {
91
+ const e = JSON.parse(t) as ProfileEvent;
92
+ if (e && typeof e.symbol === "string") events.push(e);
93
+ } catch {
94
+ /* skip bad line */
95
+ }
96
+ }
97
+ return events;
98
+ }
99
+
100
+ /** Merge Studio/playtest events into cluaupp.profile.json (additive counts). */
101
+ export function ingestProfile(
102
+ projectRoot: string,
103
+ eventsPath: string,
104
+ ): { profilePath: string; profile: CluauppProfile; merged: number } {
105
+ const eventsFile = path.resolve(eventsPath);
106
+ if (!fs.existsSync(eventsFile)) {
107
+ throw new Error(`cluaupp profile ingest: missing ${eventsFile}`);
108
+ }
109
+ const events = parseEvents(fs.readFileSync(eventsFile, "utf8"));
110
+ const profilePath = defaultProfilePath(projectRoot);
111
+ const existing = loadProfile(profilePath) || {
112
+ schemaVersion: PGO_SCHEMA_VERSION,
113
+ hot: {},
114
+ structs: {},
115
+ };
116
+ const hot = { ...(existing.hot || {}) };
117
+ const structs = { ...(existing.structs || {}) };
118
+ let merged = 0;
119
+ for (const e of events) {
120
+ const n = typeof e.count === "number" && Number.isFinite(e.count) ? e.count : 1;
121
+ if (e.kind === "struct") {
122
+ structs[e.symbol] = (structs[e.symbol] || 0) + n;
123
+ } else {
124
+ hot[e.symbol] = (hot[e.symbol] || 0) + n;
125
+ }
126
+ merged++;
127
+ }
128
+ const profile: CluauppProfile = {
129
+ schemaVersion: PGO_SCHEMA_VERSION,
130
+ generatedAt: new Date().toISOString(),
131
+ hot,
132
+ structs,
133
+ };
134
+ fs.writeFileSync(profilePath, JSON.stringify(profile, null, "\t") + "\n", "utf8");
135
+ return { profilePath, profile, merged };
136
+ }
137
+
138
+ export function rankAdvice(advice: OptimizeAdvice[], profile: CluauppProfile | null): OptimizeAdvice[] {
139
+ if (!profile) {
140
+ return advice;
141
+ }
142
+ return [...advice]
143
+ .sort((a, b) => {
144
+ const wa = profile.hot[a.target] || profile.structs?.[a.target] || 0;
145
+ const wb = profile.hot[b.target] || profile.structs?.[b.target] || 0;
146
+ return wb - wa;
147
+ })
148
+ .map((a) => {
149
+ const w = profile.hot[a.target] || profile.structs?.[a.target];
150
+ if (!w) return a;
151
+ return {
152
+ ...a,
153
+ message: `${a.message} [PGO weight=${w}]`,
154
+ severity: w > 1000 ? "warning" : a.severity,
155
+ };
156
+ });
157
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Generate project documentation from DataModel + graph + Flare + advice.
3
+ */
4
+
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+ import { buildDatamodelProfile } from "./datamodel.js";
8
+ import { buildProjectGraph } from "./project-graph.js";
9
+ import { checkFlareVersions } from "./flare-version.js";
10
+ import { collectSources } from "../clpp/paths.js";
11
+ import { adviseOptimizer } from "./optimizer-advise.js";
12
+
13
+ export function generateProjectDocs(projectRoot: string, rootDir = "src"): { outPath: string; markdown: string } {
14
+ const dm = buildDatamodelProfile(projectRoot);
15
+ const graph = buildProjectGraph(projectRoot, rootDir);
16
+ const flare = checkFlareVersions(projectRoot, rootDir);
17
+ const srcDir = path.join(projectRoot, rootDir);
18
+ const files = fs.existsSync(srcDir) ? collectSources(srcDir) : [];
19
+ const advice: string[] = [];
20
+ for (const file of files) {
21
+ const rel = path.relative(projectRoot, file).replace(/\\/g, "/");
22
+ for (const a of adviseOptimizer(fs.readFileSync(file, "utf8"), rel)) {
23
+ advice.push(`- **${a.target}** (${a.kind}): ${a.message}`);
24
+ }
25
+ }
26
+
27
+ const paths = dm
28
+ ? Object.keys(dm.byPath)
29
+ .filter((p) => p !== "DataModel")
30
+ .sort()
31
+ .map((p) => `- \`${p}\` — ${dm.byPath[p].className}`)
32
+ .join("\n")
33
+ : "_no default.project.json_";
34
+
35
+ const markdown = [
36
+ `# Cluaupp project docs`,
37
+ ``,
38
+ `Generated: ${new Date().toISOString()}`,
39
+ ``,
40
+ `## DataModel`,
41
+ ``,
42
+ paths,
43
+ ``,
44
+ `## Modules (${graph.nodes.length})`,
45
+ ``,
46
+ graph.nodes.map((n) => `- \`${n}\``).join("\n") || "_none_",
47
+ ``,
48
+ `## Dependencies`,
49
+ ``,
50
+ graph.edges.map((e) => `- \`${e.from}\` → \`${e.to}\``).join("\n") || "_none_",
51
+ ``,
52
+ graph.cycles.length ? `### Cycles\n\n${graph.cycles.map((c) => `- ${c.join(" → ")}`).join("\n")}\n` : "",
53
+ `## Flare contracts`,
54
+ ``,
55
+ flare.files.map((f) => `- \`${f.file}\` name=\`${f.name}\` version=${f.version ?? "unset"}`).join("\n") || "_none_",
56
+ ``,
57
+ `## Optimizer advice`,
58
+ ``,
59
+ advice.slice(0, 40).join("\n") || "_none_",
60
+ ``,
61
+ ].join("\n");
62
+
63
+ const outDir = path.join(projectRoot, ".cluaupp");
64
+ fs.mkdirSync(outDir, { recursive: true });
65
+ const outPath = path.join(outDir, "PROJECT.md");
66
+ fs.writeFileSync(outPath, markdown, "utf8");
67
+ return { outPath, markdown };
68
+ }
@@ -0,0 +1,75 @@
1
+ /** Project include/import/require graph + circular dependency detection. */
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { collectLanguageModulePaths, resolveModuleFile } from "../clpp/modules.js";
5
+ import { collectSources } from "../clpp/paths.js";
6
+
7
+ export interface GraphEdge {
8
+ from: string;
9
+ to: string;
10
+ }
11
+
12
+ export interface ProjectGraph {
13
+ nodes: string[];
14
+ edges: GraphEdge[];
15
+ cycles: string[][];
16
+ }
17
+
18
+ function findCycles(nodes: string[], edges: GraphEdge[]): string[][] {
19
+ const adj = new Map<string, string[]>();
20
+ for (const n of nodes) adj.set(n, []);
21
+ for (const e of edges) {
22
+ if (!adj.has(e.from)) adj.set(e.from, []);
23
+ adj.get(e.from)!.push(e.to);
24
+ }
25
+ const cycles: string[][] = [];
26
+ const visiting = new Set<string>();
27
+ const visited = new Set<string>();
28
+ const stack: string[] = [];
29
+
30
+ function dfs(n: string) {
31
+ if (visiting.has(n)) {
32
+ const i = stack.indexOf(n);
33
+ if (i >= 0) cycles.push(stack.slice(i).concat(n));
34
+ return;
35
+ }
36
+ if (visited.has(n)) return;
37
+ visiting.add(n);
38
+ stack.push(n);
39
+ for (const to of adj.get(n) || []) dfs(to);
40
+ stack.pop();
41
+ visiting.delete(n);
42
+ visited.add(n);
43
+ }
44
+ for (const n of nodes) dfs(n);
45
+ return cycles;
46
+ }
47
+
48
+ export function buildProjectGraph(projectRoot: string, rootDir = "src"): ProjectGraph {
49
+ const srcDir = path.join(projectRoot, rootDir);
50
+ const files = fs.existsSync(srcDir) ? collectSources(srcDir) : [];
51
+ const nodes: string[] = [];
52
+ const edges: GraphEdge[] = [];
53
+ const byAbs = new Map<string, string>();
54
+
55
+ for (const file of files) {
56
+ const rel = path.relative(projectRoot, file).replace(/\\/g, "/");
57
+ nodes.push(rel);
58
+ byAbs.set(path.resolve(file), rel);
59
+ }
60
+
61
+ for (const file of files) {
62
+ const rel = path.relative(projectRoot, file).replace(/\\/g, "/");
63
+ const source = fs.readFileSync(file, "utf8");
64
+ for (const mod of collectLanguageModulePaths(source)) {
65
+ const resolved = resolveModuleFile(mod, file, srcDir);
66
+ if (!resolved) continue;
67
+ const hit = byAbs.get(path.resolve(resolved));
68
+ if (hit && hit !== rel) {
69
+ edges.push({ from: rel, to: hit });
70
+ }
71
+ }
72
+ }
73
+
74
+ return { nodes, edges, cycles: findCycles(nodes, edges) };
75
+ }
@@ -0,0 +1,78 @@
1
+ import type { RobloxTargetProfile } from "../api/model.js";
2
+ import { ROBLOX_TARGET_SCHEMA_VERSION } from "../api/model.js";
3
+ import { buildRobloxTargetProfile } from "../api/build-profile.js";
4
+
5
+ /** Cluaupp-owned run contexts — never push these into CLPP core. */
6
+ export type RunContext = "Server" | "Client" | "Plugin" | "Shared";
7
+
8
+ export const FILE_TAG_TO_CONTEXT: Record<string, RunContext> = {
9
+ ".server": "Server",
10
+ ".client": "Client",
11
+ ".plugin": "Plugin",
12
+ };
13
+
14
+ export function runContextFromFileName(fileName: string): RunContext {
15
+ const lower = fileName.toLowerCase();
16
+ if (lower.includes(".server.")) {
17
+ return "Server";
18
+ }
19
+ if (lower.includes(".client.")) {
20
+ return "Client";
21
+ }
22
+ if (lower.includes(".plugin.")) {
23
+ return "Plugin";
24
+ }
25
+ return "Shared";
26
+ }
27
+
28
+ export interface RobloxProjectModel {
29
+ kind: "roblox";
30
+ rojoProjectPath?: string;
31
+ libsFolderName: "CluauppLibs";
32
+ /** Historical CLPP postprocess name — remapped in Cluaupp only. */
33
+ legacyLibsAlias: "ClppLibs";
34
+ }
35
+
36
+ export function defaultRobloxProject(): RobloxProjectModel {
37
+ return {
38
+ kind: "roblox",
39
+ libsFolderName: "CluauppLibs",
40
+ legacyLibsAlias: "ClppLibs",
41
+ };
42
+ }
43
+
44
+ /**
45
+ * Target-side GetService\<T\> — **Cluaupp Roblox Target feature**, not a CL++ language rule.
46
+ * CL++ only provides generic-call syntax (`GetService<Players>()`); Cluaupp maps T via the
47
+ * Canonical Registry, Context Safety, and headers under `#include <clpp/roblox.clh>`.
48
+ */
49
+ export function resolveGetServiceType(typeName: string, profile?: RobloxTargetProfile): string | null {
50
+ const p = profile || buildRobloxTargetProfile({ skipValidate: true });
51
+ if (p.services[typeName]) {
52
+ return typeName;
53
+ }
54
+ if (p.classes[typeName]?.service || p.classes[typeName]?.creation.service) {
55
+ return typeName;
56
+ }
57
+ return null;
58
+ }
59
+
60
+ /** True when `name` is a registered Roblox service class. */
61
+ export function isRegisteredService(name: string, profile?: RobloxTargetProfile): boolean {
62
+ return resolveGetServiceType(name, profile) != null;
63
+ }
64
+
65
+ export function targetProfileSummary(): {
66
+ schemaVersion: number;
67
+ target: "roblox";
68
+ classes: number;
69
+ services: number;
70
+ } {
71
+ const profile = buildRobloxTargetProfile({ skipValidate: true });
72
+ return {
73
+ schemaVersion: ROBLOX_TARGET_SCHEMA_VERSION,
74
+ target: "roblox",
75
+ classes: Object.keys(profile.classes).length,
76
+ services: Object.keys(profile.services).length,
77
+ };
78
+ }
@@ -0,0 +1,40 @@
1
+ /** Static security patterns (CLUAU-SEC*) — not a runtime anti-cheat. */
2
+ import { runContextFromFileName } from "./roblox.js";
3
+ import { type PlatformDiagnostic, lineColAt, stripCommentsKeepStrings } from "./diagnostics.js";
4
+
5
+ const CLIENT_PATTERNS: Array<{ code: string; re: RegExp; message: string }> = [
6
+ { code: "CLUAU_SEC001", re: /\bCoins\s*=/, message: "Client appears to assign Coins — server must own currency" },
7
+ { code: "CLUAU_SEC002", re: /\bDamage\s*=\s*[^=]/, message: "Client appears to set Damage — prefer server authority" },
8
+ { code: "CLUAU_SEC003", re: /FireServer\s*\([^)]*Instance/, message: "Remote may accept unrestricted Instance from client" },
9
+ { code: "CLUAU_SEC004", re: /Inventory.*=.*FireServer|FireServer.*Inventory/, message: "Client-supplied inventory result smell" },
10
+ ];
11
+
12
+ const SERVER_PATTERNS: Array<{ code: string; re: RegExp; message: string }> = [
13
+ { code: "CLUAU_SEC010", re: /FireServer/, message: "Server calling FireServer is inverted authority" },
14
+ {
15
+ code: "CLUAU_SEC011",
16
+ re: /\.Position\b[^\n]*player|player[^\n]*\.Position/,
17
+ message: "Server may be trusting client Position — validate or ignore client transform",
18
+ },
19
+ ];
20
+
21
+ export function checkSecurity(source: string, fileName: string): PlatformDiagnostic[] {
22
+ const ctx = runContextFromFileName(fileName);
23
+ const clean = stripCommentsKeepStrings(source);
24
+ const out: PlatformDiagnostic[] = [];
25
+ const list = ctx === "Client" ? CLIENT_PATTERNS : ctx === "Server" ? SERVER_PATTERNS : [];
26
+ for (const p of list) {
27
+ const m = p.re.exec(clean);
28
+ if (!m) continue;
29
+ const { line, column } = lineColAt(clean, m.index);
30
+ out.push({
31
+ code: p.code,
32
+ message: p.message,
33
+ line,
34
+ column,
35
+ severity: "warning",
36
+ file: fileName,
37
+ });
38
+ }
39
+ return out;
40
+ }
@@ -0,0 +1,36 @@
1
+ /** Minimal source map: CL++ path ↔ emitted Luau (1:1 file mapping for v1). */
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+
5
+ export interface CluauppSourceMap {
6
+ version: 1;
7
+ file: string;
8
+ sourceRoot: string;
9
+ sources: string[];
10
+ mappings: string;
11
+ cluaupp: {
12
+ source: string;
13
+ generated: string;
14
+ };
15
+ }
16
+
17
+ export function writeSourceMap(opts: {
18
+ outLuauPath: string;
19
+ sourceClppRel: string;
20
+ projectRoot: string;
21
+ }): string {
22
+ const map: CluauppSourceMap = {
23
+ version: 1,
24
+ file: path.basename(opts.outLuauPath),
25
+ sourceRoot: opts.projectRoot.replace(/\\/g, "/"),
26
+ sources: [opts.sourceClppRel.replace(/\\/g, "/")],
27
+ mappings: "AAAA", // identity stub — refine when clpp emits VLQ maps
28
+ cluaupp: {
29
+ source: opts.sourceClppRel.replace(/\\/g, "/"),
30
+ generated: path.relative(opts.projectRoot, opts.outLuauPath).replace(/\\/g, "/"),
31
+ },
32
+ };
33
+ const mapPath = opts.outLuauPath + ".map.json";
34
+ fs.writeFileSync(mapPath, JSON.stringify(map, null, "\t") + "\n", "utf8");
35
+ return mapPath;
36
+ }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Local HTTP bridge: Studio plugin ↔ IDE (VS Code / Cursor).
3
+ * Default port 3847. Studio POSTs open requests; IDE polls /events.
4
+ */
5
+
6
+ import http from "node:http";
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+ import { EventEmitter } from "node:events";
10
+
11
+ export const BRIDGE_DEFAULT_PORT = 3847;
12
+
13
+ export interface BridgeEvent {
14
+ id: number;
15
+ at: string;
16
+ type: "open" | "select" | "error" | "ping";
17
+ path?: string;
18
+ line?: number;
19
+ message?: string;
20
+ instancePath?: string;
21
+ }
22
+
23
+ export interface BridgeServer {
24
+ port: number;
25
+ close: () => void;
26
+ onEvent: (fn: (ev: BridgeEvent) => void) => () => void;
27
+ getEventsSince: (id: number) => BridgeEvent[];
28
+ }
29
+
30
+ export function startStudioBridge(opts: {
31
+ port?: number;
32
+ projectRoot?: string;
33
+ onEvent?: (ev: BridgeEvent) => void;
34
+ } = {}): BridgeServer {
35
+ const port = opts.port ?? BRIDGE_DEFAULT_PORT;
36
+ const bus = new EventEmitter();
37
+ const events: BridgeEvent[] = [];
38
+ let nextId = 1;
39
+
40
+ const push = (partial: Omit<BridgeEvent, "id" | "at">) => {
41
+ const ev: BridgeEvent = {
42
+ id: nextId++,
43
+ at: new Date().toISOString(),
44
+ ...partial,
45
+ };
46
+ events.push(ev);
47
+ if (events.length > 200) events.shift();
48
+ bus.emit("event", ev);
49
+ opts.onEvent?.(ev);
50
+ return ev;
51
+ };
52
+
53
+ const server = http.createServer((req, res) => {
54
+ const url = new URL(req.url || "/", `http://127.0.0.1:${port}`);
55
+ res.setHeader("Access-Control-Allow-Origin", "*");
56
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
57
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
58
+ if (req.method === "OPTIONS") {
59
+ res.writeHead(204);
60
+ res.end();
61
+ return;
62
+ }
63
+
64
+ if (req.method === "GET" && url.pathname === "/health") {
65
+ res.writeHead(200, { "Content-Type": "application/json" });
66
+ res.end(JSON.stringify({ ok: true, service: "cluaupp-bridge", port }));
67
+ return;
68
+ }
69
+
70
+ if (req.method === "GET" && url.pathname === "/events") {
71
+ const since = Number(url.searchParams.get("since") || "0");
72
+ const batch = events.filter((e) => e.id > since);
73
+ res.writeHead(200, { "Content-Type": "application/json" });
74
+ res.end(JSON.stringify({ events: batch }));
75
+ return;
76
+ }
77
+
78
+ if (req.method === "POST" && url.pathname === "/open") {
79
+ let body = "";
80
+ req.on("data", (c) => {
81
+ body += c;
82
+ if (body.length > 1_000_000) req.destroy();
83
+ });
84
+ req.on("end", () => {
85
+ try {
86
+ const data = JSON.parse(body || "{}") as {
87
+ path?: string;
88
+ line?: number;
89
+ instancePath?: string;
90
+ type?: BridgeEvent["type"];
91
+ message?: string;
92
+ };
93
+ const rel = (data.path || "").replace(/\\/g, "/");
94
+ if (opts.projectRoot && rel && !rel.includes("..")) {
95
+ const abs = path.resolve(opts.projectRoot, rel);
96
+ if (!abs.startsWith(path.resolve(opts.projectRoot))) {
97
+ res.writeHead(400);
98
+ res.end("bad path");
99
+ return;
100
+ }
101
+ if (!fs.existsSync(abs) && !rel.endsWith(".clpp") && !rel.endsWith(".clh")) {
102
+ /* still emit — file may be unsaved emit path */
103
+ }
104
+ }
105
+ const ev = push({
106
+ type: data.type || "open",
107
+ path: rel || undefined,
108
+ line: data.line,
109
+ instancePath: data.instancePath,
110
+ message: data.message,
111
+ });
112
+ res.writeHead(200, { "Content-Type": "application/json" });
113
+ res.end(JSON.stringify({ ok: true, id: ev.id }));
114
+ } catch (err) {
115
+ res.writeHead(400);
116
+ res.end(String(err instanceof Error ? err.message : err));
117
+ }
118
+ });
119
+ return;
120
+ }
121
+
122
+ res.writeHead(404);
123
+ res.end("not found");
124
+ });
125
+
126
+ server.listen(port, "127.0.0.1");
127
+
128
+ return {
129
+ port,
130
+ close: () => {
131
+ server.close();
132
+ bus.removeAllListeners();
133
+ },
134
+ onEvent: (fn) => {
135
+ bus.on("event", fn);
136
+ return () => bus.off("event", fn);
137
+ },
138
+ getEventsSince: (id) => events.filter((e) => e.id > id),
139
+ };
140
+ }
package/src/types.ts CHANGED
@@ -26,6 +26,8 @@ export interface RequireBinding {
26
26
  export interface CompileOptions {
27
27
  strict?: boolean;
28
28
  architecture?: boolean;
29
+ /** Pass through to clpp (default true). */
30
+ optimize?: boolean;
29
31
  rootDir?: string;
30
32
  outDir?: string;
31
33
  filePath?: string;
@@ -44,6 +46,12 @@ export interface ProjectConfig {
44
46
  outDir: string;
45
47
  strict: boolean;
46
48
  architecture: boolean;
49
+ rules?: {
50
+ clientCannotImport?: string[];
51
+ sharedCannotUse?: string[];
52
+ serverCannotImport?: string[];
53
+ };
54
+ components?: Array<{ path: string; children: Record<string, string> }>;
47
55
  }
48
56
 
49
57
  export interface BuildOptions {
@@ -54,6 +62,12 @@ export interface BuildOptions {
54
62
  analyze?: boolean;
55
63
  rojo?: string;
56
64
  strict?: boolean;
65
+ /** Fail if api lock / Flare versions / component contracts are dirty. */
66
+ frozen?: boolean;
67
+ /** Skip transpile when source fingerprint matches `.cluaupp/compile-cache` (default true). */
68
+ incremental?: boolean;
69
+ /** Parallel transpile workers for async build (default: CPU-bounded). Set 1 to disable. */
70
+ jobs?: number;
57
71
  }
58
72
 
59
73
  export interface BuildResult {