cluaupp 1.4.1 → 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 (340) hide show
  1. package/CHANGELOG.md +55 -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 +19 -0
  26. package/editors/vscode/extension.js +222 -21
  27. package/editors/vscode/icons/axiom.svg +4 -0
  28. package/editors/vscode/icons/bloom.svg +5 -0
  29. package/editors/vscode/icons/flare.svg +5 -0
  30. package/editors/vscode/icons/helm.svg +4 -0
  31. package/editors/vscode/icons/hive.svg +5 -0
  32. package/editors/vscode/icons/mint.svg +4 -0
  33. package/editors/vscode/icons/shift.svg +6 -0
  34. package/editors/vscode/language-configuration-schema.json +24 -0
  35. package/editors/vscode/language-configuration.json +21 -0
  36. package/editors/vscode/package.json +220 -6
  37. package/editors/vscode/snippets/axiom.json +12 -0
  38. package/editors/vscode/snippets/bloom.json +12 -0
  39. package/editors/vscode/snippets/flare.json +27 -0
  40. package/editors/vscode/snippets/helm.json +12 -0
  41. package/editors/vscode/snippets/hive.json +12 -0
  42. package/editors/vscode/snippets/mint.json +12 -0
  43. package/editors/vscode/snippets/shift.json +17 -0
  44. package/editors/vscode/syntaxes/axiom.tmLanguage.json +5 -0
  45. package/editors/vscode/syntaxes/bloom.tmLanguage.json +5 -0
  46. package/editors/vscode/syntaxes/flare.tmLanguage.json +75 -0
  47. package/editors/vscode/syntaxes/helm.tmLanguage.json +5 -0
  48. package/editors/vscode/syntaxes/hive.tmLanguage.json +5 -0
  49. package/editors/vscode/syntaxes/mint.tmLanguage.json +5 -0
  50. package/editors/vscode/syntaxes/schema.tmLanguage.json +89 -0
  51. package/editors/vscode/syntaxes/shift.tmLanguage.json +5 -0
  52. package/examples/game/.cluaupp/compile-cache/ReplicatedStorage__Shared__Constants__Datas__TemplateData.clh.json +1 -0
  53. package/examples/game/.cluaupp/compile-cache/ServerScriptService__Boot__DataBoot.server.clpp.json +1 -0
  54. package/examples/game/.cluaupp/compile-cache/ServerScriptService__Configuration__ServerConfig.clp.json +1 -0
  55. package/examples/game/.cluaupp/compile-cache/ServerScriptService__Handlers__Guard.server.clpp.json +1 -0
  56. package/examples/game/.cluaupp/compile-cache/ServerScriptService__Handlers__PlayerHandler.server.clpp.json +1 -0
  57. package/examples/game/.cluaupp/compile-cache/StarterPlayer__StarterCharacterScripts__Character__CharacterReady.client.clpp.json +1 -0
  58. package/examples/game/.cluaupp/compile-cache/StarterPlayer__StarterPlayerScripts__Controllers__DataController.client.clpp.json +1 -0
  59. package/examples/game/.cluaupp/compile-cache/index.json +12 -0
  60. package/examples/game/.cluaupp/datamodel-profile.json +94 -0
  61. package/examples/game/.vscode/extensions.json +3 -1
  62. package/examples/game/.vscode/settings.json +13 -1
  63. package/examples/game/include/clpp/datatypes.clh +337 -373
  64. package/examples/game/include/clpp/generated/datamodel.clh +25 -0
  65. package/examples/game/include/clpp/generated/enums.clh +1 -1
  66. package/examples/game/include/clpp/generated/instances.clh +41135 -41064
  67. package/examples/game/include/clpp/libs/axiom.clh +1 -1
  68. package/examples/game/include/clpp/libs/math.clh +14 -0
  69. package/examples/game/include/clpp/libs/roster.clh +6 -0
  70. package/examples/game/include/clpp/libs/spring.clh +17 -0
  71. package/examples/game/include/clpp/libs/ward.clh +1 -0
  72. package/examples/game/include/clpp/roblox.clh +3 -4
  73. package/examples/game/libs/ArrayIndexer/init.luau +209 -0
  74. package/examples/game/libs/Axiom/init.luau +608 -0
  75. package/examples/game/libs/Bloom/init.luau +433 -0
  76. package/examples/game/libs/Coil/init.luau +215 -0
  77. package/examples/game/libs/Crest/init.luau +426 -0
  78. package/examples/game/libs/Echo/init.luau +417 -0
  79. package/examples/game/libs/Ember/init.luau +365 -0
  80. package/examples/game/libs/Flare/init.luau +717 -0
  81. package/examples/game/libs/Gleam/init.luau +729 -0
  82. package/examples/game/libs/Guide/init.luau +215 -0
  83. package/examples/game/libs/Helm/init.luau +377 -0
  84. package/examples/game/libs/Hive/init.luau +241 -0
  85. package/examples/game/libs/Keep/Client.luau +114 -0
  86. package/examples/game/libs/Keep/Data.luau +883 -0
  87. package/examples/game/libs/Keep/Engine.luau +211 -0
  88. package/examples/game/libs/Keep/Enum.luau +6 -0
  89. package/examples/game/libs/Keep/Enums.luau +10 -0
  90. package/examples/game/libs/Keep/Path.luau +236 -0
  91. package/examples/game/libs/Keep/Replicator.luau +298 -0
  92. package/examples/game/libs/Keep/Server.luau +251 -0
  93. package/examples/game/libs/Keep/Store.luau +652 -0
  94. package/examples/game/libs/Keep/Trade.luau +455 -0
  95. package/examples/game/libs/Keep/init.luau +205 -0
  96. package/examples/game/libs/Lens/init.luau +609 -0
  97. package/examples/game/libs/Mint/init.luau +279 -0
  98. package/examples/game/libs/Net/init.luau +156 -0
  99. package/examples/game/libs/Net/src/codec.luau +224 -0
  100. package/examples/game/libs/Occlude/init.luau +117 -0
  101. package/examples/game/libs/Pin/init.luau +309 -0
  102. package/examples/game/libs/Promise/LICENSE +21 -0
  103. package/examples/game/libs/Promise/_impl/LICENSE +21 -0
  104. package/examples/game/libs/Promise/_impl/init.lua +1991 -0
  105. package/examples/game/libs/Promise/init.lua +1990 -0
  106. package/examples/game/libs/Promise/init.luau +13 -0
  107. package/examples/game/libs/Promise/types.luau +46 -0
  108. package/examples/game/libs/Roster/init.luau +686 -0
  109. package/examples/game/libs/SOURCES.md +31 -0
  110. package/examples/game/libs/Shift/init.luau +238 -0
  111. package/examples/game/libs/Spark/init.luau +487 -0
  112. package/examples/game/libs/Stage/init.luau +178 -0
  113. package/examples/game/libs/Sweep/init.luau +259 -0
  114. package/examples/game/libs/Trace/init.luau +213 -0
  115. package/examples/game/libs/Ward/init.luau +211 -0
  116. package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau +29 -0
  117. package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau.map.json +13 -0
  118. package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau +83 -0
  119. package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau.map.json +13 -0
  120. package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau +18 -0
  121. package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau.map.json +13 -0
  122. package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau +14 -0
  123. package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau.map.json +13 -0
  124. package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau +15 -0
  125. package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau.map.json +13 -0
  126. package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau +50 -0
  127. package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau.map.json +13 -0
  128. package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau +12 -0
  129. package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau.map.json +13 -0
  130. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau +16 -0
  131. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau.map.json +13 -0
  132. package/examples/game/src/ReplicatedStorage/Shared/Net/Net.clh +26 -0
  133. package/examples/game/src/ReplicatedStorage/Shared/Net/Net.flare +5 -0
  134. package/examples/game/src/ServerScriptService/Boot/DataBoot.server.clpp +2 -2
  135. package/examples/game/src/ServerScriptService/Handlers/PlayerHandler.server.clpp +1 -1
  136. package/examples/game/stylua.toml +9 -0
  137. package/generated/api/build-profile.js +115 -0
  138. package/generated/api/cache.js +44 -0
  139. package/generated/api/coverage.js +32 -0
  140. package/generated/api/diff.js +125 -0
  141. package/generated/api/generators/clpp-headers.js +353 -0
  142. package/generated/api/generators/docs-reference.js +7 -0
  143. package/generated/api/generators/lsp-index.js +81 -0
  144. package/generated/api/generators/luau-defs.js +94 -0
  145. package/generated/api/ide.js +76 -0
  146. package/generated/api/identifier.js +47 -0
  147. package/generated/api/index.js +324 -0
  148. package/generated/api/loader.js +57 -0
  149. package/generated/api/merge.js +88 -0
  150. package/generated/api/model.js +5 -0
  151. package/generated/api/normalize.js +231 -0
  152. package/generated/api/overrides-validate.js +58 -0
  153. package/generated/api/perf.js +12 -0
  154. package/generated/api/registry.js +136 -0
  155. package/generated/api/release.js +44 -0
  156. package/generated/api/resolver.js +250 -0
  157. package/generated/api/sources/aux-types-diff.js +75 -0
  158. package/generated/api/sources/client-tracker.js +11 -0
  159. package/generated/api/sources/creator-docs.js +42 -0
  160. package/generated/api/sources/luau-types.js +34 -0
  161. package/generated/api/sources/mini-dump.js +17 -0
  162. package/generated/api/sources/studio-api.js +8 -0
  163. package/generated/api/sources/studio-apiv2.js +11 -0
  164. package/generated/api/sources/studio-full-api.js +8 -0
  165. package/generated/api/studio-smoke.js +80 -0
  166. package/generated/api/type-parse.js +49 -0
  167. package/generated/api/understander.js +51 -0
  168. package/generated/api/validate.js +135 -0
  169. package/generated/api-cli.js +421 -0
  170. package/generated/cli.js +17 -6
  171. package/generated/clpp/contract.js +3 -3
  172. package/generated/clpp/format-luau.js +236 -0
  173. package/generated/clpp/index.js +13 -1
  174. package/generated/clpp/modules.js +227 -0
  175. package/generated/clpp/postprocess.js +86 -5
  176. package/generated/clpp/rewrite-janitor.js +145 -0
  177. package/generated/clpp/runner.js +14 -2
  178. package/generated/compile.js +82 -0
  179. package/generated/flare/index.js +15 -3
  180. package/generated/flare/language.js +561 -0
  181. package/generated/flare/parse.js +19 -7
  182. package/generated/intellisense.js +76 -2
  183. package/generated/lsp.js +118 -7
  184. package/generated/native/index.js +217 -16
  185. package/generated/target/authority-check.js +136 -0
  186. package/generated/target/authority-graph.js +74 -0
  187. package/generated/target/build-cache.js +112 -0
  188. package/generated/target/capabilities.js +89 -0
  189. package/generated/target/capability-profile.js +30 -0
  190. package/generated/target/component-contracts.js +60 -0
  191. package/generated/target/context-check.js +160 -0
  192. package/generated/target/datamodel-check.js +170 -0
  193. package/generated/target/datamodel-generate.js +74 -0
  194. package/generated/target/datamodel.js +233 -0
  195. package/generated/target/diagnostics.js +63 -0
  196. package/generated/target/doctor.js +152 -0
  197. package/generated/target/flare-version.js +58 -0
  198. package/generated/target/get-service.js +69 -0
  199. package/generated/target/lifetime-check.js +47 -0
  200. package/generated/target/luau-stamp.js +39 -0
  201. package/generated/target/optimizer-advise.js +88 -0
  202. package/generated/target/optimizer-apply.js +44 -0
  203. package/generated/target/optimizer-soa.js +254 -0
  204. package/generated/target/parallel-check.js +136 -0
  205. package/generated/target/pgo.js +148 -0
  206. package/generated/target/project-docs.js +69 -0
  207. package/generated/target/project-graph.js +71 -0
  208. package/generated/target/roblox.js +63 -0
  209. package/generated/target/security-check.js +41 -0
  210. package/generated/target/source-map.js +25 -0
  211. package/generated/target/studio-bridge.js +112 -0
  212. package/generated/utils/project.js +178 -8
  213. package/generated/utils/rojo-mapper.js +11 -3
  214. package/include/clpp/datatypes.clh +337 -373
  215. package/include/clpp/generated/enums.clh +1 -1
  216. package/include/clpp/generated/instances.clh +41135 -41064
  217. package/include/clpp/libs/axiom.clh +1 -1
  218. package/include/clpp/libs/hive.clh +1 -0
  219. package/include/clpp/libs/keep.clh +2 -0
  220. package/include/clpp/libs/math.clh +14 -0
  221. package/include/clpp/libs/roster.clh +6 -0
  222. package/include/clpp/libs/spring.clh +17 -0
  223. package/include/clpp/libs/ward.clh +1 -0
  224. package/include/clpp/roblox.clh +3 -4
  225. package/package.json +10 -3
  226. package/plugins/CluauppNav/Plugin.luau +207 -0
  227. package/plugins/CluauppNav/ProfileLogger.luau +27 -0
  228. package/plugins/CluauppNav/README.md +24 -0
  229. package/runtime/Axiom/init.luau +10 -7
  230. package/runtime/Coil/init.luau +8 -2
  231. package/runtime/Crest/init.luau +1 -0
  232. package/runtime/Flare/init.luau +2 -1
  233. package/runtime/Gleam/init.luau +15 -4
  234. package/runtime/Hive/init.luau +36 -14
  235. package/runtime/Pin/init.luau +5 -2
  236. package/runtime/Roster/init.luau +8 -0
  237. package/runtime/Spark/init.luau +3 -0
  238. package/runtime/Sweep/init.luau +2 -0
  239. package/runtime/Trace/init.luau +3 -0
  240. package/runtime/Ward/init.luau +38 -5
  241. package/scripts/postinstall-editor.js +17 -0
  242. package/skills/README.md +13 -0
  243. package/skills/clpp-hive-flare/SKILL.md +79 -0
  244. package/skills/clpp-language/SKILL.md +123 -0
  245. package/skills/clpp-language/compiler.md +89 -0
  246. package/skills/clpp-style/SKILL.md +95 -0
  247. package/src/api/build-profile.ts +138 -0
  248. package/src/api/cache.ts +45 -0
  249. package/src/api/coverage.ts +46 -0
  250. package/src/api/diff.ts +143 -0
  251. package/src/api/generators/clpp-headers.ts +377 -0
  252. package/src/api/generators/docs-reference.ts +11 -0
  253. package/src/api/generators/lsp-index.ts +106 -0
  254. package/src/api/generators/luau-defs.ts +98 -0
  255. package/src/api/ide.ts +87 -0
  256. package/src/api/identifier.ts +47 -0
  257. package/src/api/index.ts +269 -0
  258. package/src/api/loader.ts +60 -0
  259. package/src/api/merge.ts +126 -0
  260. package/src/api/model.ts +283 -0
  261. package/src/api/normalize.ts +257 -0
  262. package/src/api/overrides-validate.ts +58 -0
  263. package/src/api/perf.ts +15 -0
  264. package/src/api/registry.ts +162 -0
  265. package/src/api/release.ts +47 -0
  266. package/src/api/resolver.ts +255 -0
  267. package/src/api/sources/aux-types-diff.ts +81 -0
  268. package/src/api/sources/client-tracker.ts +10 -0
  269. package/src/api/sources/creator-docs.ts +37 -0
  270. package/src/api/sources/luau-types.ts +30 -0
  271. package/src/api/sources/mini-dump.ts +50 -0
  272. package/src/api/sources/studio-api.ts +7 -0
  273. package/src/api/sources/studio-apiv2.ts +10 -0
  274. package/src/api/sources/studio-full-api.ts +7 -0
  275. package/src/api/studio-smoke.ts +82 -0
  276. package/src/api/type-parse.ts +48 -0
  277. package/src/api/understander.ts +72 -0
  278. package/src/api/validate.ts +160 -0
  279. package/src/api-cli.ts +493 -0
  280. package/src/api.generated.js +37 -22
  281. package/src/cli.ts +22 -7
  282. package/src/clpp/contract.ts +34 -3
  283. package/src/clpp/format-luau.ts +262 -0
  284. package/src/clpp/index.ts +13 -2
  285. package/src/clpp/modules.ts +252 -0
  286. package/src/clpp/postprocess.ts +100 -5
  287. package/src/clpp/rewrite-janitor.ts +156 -0
  288. package/src/clpp/runner.ts +14 -2
  289. package/src/compile.ts +87 -0
  290. package/src/flare/index.ts +20 -1
  291. package/src/flare/language.ts +708 -0
  292. package/src/flare/parse.ts +20 -7
  293. package/src/flare/types.ts +2 -0
  294. package/src/intellisense.ts +76 -2
  295. package/src/lsp.ts +151 -8
  296. package/src/native/index.ts +225 -16
  297. package/src/target/authority-check.ts +162 -0
  298. package/src/target/authority-graph.ts +80 -0
  299. package/src/target/build-cache.ts +129 -0
  300. package/src/target/capabilities.ts +115 -0
  301. package/src/target/capability-profile.ts +25 -0
  302. package/src/target/component-contracts.ts +64 -0
  303. package/src/target/context-check.ts +177 -0
  304. package/src/target/datamodel-check.ts +188 -0
  305. package/src/target/datamodel-generate.ts +78 -0
  306. package/src/target/datamodel.ts +261 -0
  307. package/src/target/diagnostics.ts +67 -0
  308. package/src/target/doctor.ts +171 -0
  309. package/src/target/flare-version.ts +61 -0
  310. package/src/target/get-service.ts +85 -0
  311. package/src/target/lifetime-check.ts +45 -0
  312. package/src/target/luau-stamp.ts +33 -0
  313. package/src/target/optimizer-advise.ts +93 -0
  314. package/src/target/optimizer-apply.ts +63 -0
  315. package/src/target/optimizer-soa.ts +270 -0
  316. package/src/target/parallel-check.ts +135 -0
  317. package/src/target/pgo.ts +157 -0
  318. package/src/target/project-docs.ts +68 -0
  319. package/src/target/project-graph.ts +75 -0
  320. package/src/target/roblox.ts +78 -0
  321. package/src/target/security-check.ts +40 -0
  322. package/src/target/source-map.ts +36 -0
  323. package/src/target/studio-bridge.ts +140 -0
  324. package/src/types.ts +14 -0
  325. package/src/utils/project.ts +183 -9
  326. package/src/utils/rojo-mapper.ts +12 -3
  327. package/stylua.toml +9 -0
  328. package/templates/game/.vscode/extensions.json +3 -1
  329. package/templates/game/.vscode/settings.json +8 -1
  330. package/templates/game/include/clpp/datatypes.clh +337 -373
  331. package/templates/game/include/clpp/generated/enums.clh +1 -1
  332. package/templates/game/include/clpp/generated/instances.clh +41135 -41064
  333. package/templates/game/include/clpp/libs/axiom.clh +1 -1
  334. package/templates/game/include/clpp/libs/roster.clh +6 -0
  335. package/templates/game/include/clpp/libs/ward.clh +1 -0
  336. package/templates/game/include/clpp/roblox.clh +3 -4
  337. package/templates/game/src/ReplicatedStorage/Shared/Net/Net.flare +5 -0
  338. package/templates/game/src/ServerScriptService/Boot/DataBoot.server.clpp +2 -2
  339. package/templates/game/src/ServerScriptService/Handlers/PlayerHandler.server.clpp +1 -1
  340. package/templates/game/stylua.toml +9 -0
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Authority / architecture rules — import boundaries (CLUAU-AUTH*).
3
+ */
4
+
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+ import { runContextFromFileName, type RunContext } from "./roblox.js";
8
+ import { type PlatformDiagnostic, lineColAt, stripCommentsKeepStrings } from "./diagnostics.js";
9
+
10
+ export interface ArchitectureRules {
11
+ clientCannotImport?: string[];
12
+ sharedCannotUse?: string[];
13
+ serverCannotImport?: string[];
14
+ }
15
+
16
+ export interface ComponentContract {
17
+ /** Instance path suffix or exact path, e.g. "Enemy" or "Workspace.Mobs.Enemy" */
18
+ path: string;
19
+ children: Record<string, string>; // name → expected className (or BasePart etc.)
20
+ }
21
+
22
+ export interface PlatformPolicy {
23
+ rules?: ArchitectureRules;
24
+ components?: ComponentContract[];
25
+ }
26
+
27
+ export function loadPlatformPolicy(projectRoot: string): PlatformPolicy {
28
+ const file = path.join(projectRoot, "cluaupp.config.json");
29
+ if (!fs.existsSync(file)) {
30
+ return defaultPolicy();
31
+ }
32
+ try {
33
+ const raw = JSON.parse(fs.readFileSync(file, "utf8")) as Record<string, unknown>;
34
+ const rules = (raw.rules as ArchitectureRules) || {};
35
+ const components = Array.isArray(raw.components) ? (raw.components as ComponentContract[]) : [];
36
+ return {
37
+ rules: { ...defaultPolicy().rules, ...rules },
38
+ components: components.length ? components : defaultPolicy().components,
39
+ };
40
+ } catch {
41
+ return defaultPolicy();
42
+ }
43
+ }
44
+
45
+ function defaultPolicy(): PlatformPolicy {
46
+ return {
47
+ rules: {
48
+ clientCannotImport: ["ServerScriptService", "ServerStorage"],
49
+ sharedCannotUse: ["DataStoreService", "MessagingService", "ServerStorage"],
50
+ serverCannotImport: [],
51
+ },
52
+ components: [],
53
+ };
54
+ }
55
+
56
+ const INCLUDE_RE = /#include\s*[<"]([^>"]+)[>"]/g;
57
+ const IMPORT_RE = /\bimport\s*\{[^}]*\}\s*from\s*"([^"]+)"/g;
58
+ const REQUIRE_RE = /require\s*\(\s*([^)]+)\)/g;
59
+ const GET_SERVICE = /GetService\s*(?:<\s*([A-Za-z_]\w*)\s*>|\(\s*"([A-Za-z_]\w*)"\s*\))/g;
60
+
61
+ export function checkAuthority(
62
+ source: string,
63
+ fileName: string,
64
+ policy: PlatformPolicy = defaultPolicy(),
65
+ ): PlatformDiagnostic[] {
66
+ const ctx = runContextFromFileName(fileName);
67
+ const clean = stripCommentsKeepStrings(source);
68
+ const out: PlatformDiagnostic[] = [];
69
+ const rules = policy.rules || {};
70
+
71
+ const bannedImports =
72
+ ctx === "Client"
73
+ ? rules.clientCannotImport || []
74
+ : ctx === "Shared"
75
+ ? []
76
+ : ctx === "Server"
77
+ ? rules.serverCannotImport || []
78
+ : [];
79
+
80
+ const checkToken = (token: string, index: number, code: string, message: string) => {
81
+ const { line, column } = lineColAt(clean, index);
82
+ out.push({ code, message, line, column, severity: "error", file: fileName });
83
+ };
84
+
85
+ for (const banned of bannedImports) {
86
+ const re = new RegExp(`\\b${banned}\\b`);
87
+ INCLUDE_RE.lastIndex = 0;
88
+ let m: RegExpExecArray | null;
89
+ while ((m = INCLUDE_RE.exec(clean))) {
90
+ if (re.test(m[1])) {
91
+ checkToken(m[1], m.index, "CLUAU_AUTH001", `Architecture: ${ctx} must not import/include path involving ${banned}`);
92
+ }
93
+ }
94
+ IMPORT_RE.lastIndex = 0;
95
+ while ((m = IMPORT_RE.exec(clean))) {
96
+ if (re.test(m[1])) {
97
+ checkToken(m[1], m.index, "CLUAU_AUTH001", `Architecture: ${ctx} must not import path involving ${banned}`);
98
+ }
99
+ }
100
+ REQUIRE_RE.lastIndex = 0;
101
+ while ((m = REQUIRE_RE.exec(clean))) {
102
+ if (re.test(m[1])) {
103
+ checkToken(m[1], m.index, "CLUAU_AUTH001", `Architecture: ${ctx} must not require ${banned}`);
104
+ }
105
+ }
106
+ if (re.test(clean) && (clean.includes(`"${banned}`) || clean.includes(`'${banned}`) || clean.includes(`<${banned}`))) {
107
+ /* covered above */
108
+ }
109
+ }
110
+
111
+ if (ctx === "Shared" || ctx === "Client") {
112
+ const forbiddenServices = rules.sharedCannotUse || [];
113
+ GET_SERVICE.lastIndex = 0;
114
+ let m: RegExpExecArray | null;
115
+ while ((m = GET_SERVICE.exec(clean))) {
116
+ const svc = m[1] || m[2];
117
+ if (svc && forbiddenServices.includes(svc) && ctx === "Shared") {
118
+ const { line, column } = lineColAt(clean, m.index);
119
+ out.push({
120
+ code: "CLUAU_AUTH002",
121
+ message: `Architecture: Shared must not use ${svc}`,
122
+ line,
123
+ column,
124
+ severity: "error",
125
+ file: fileName,
126
+ });
127
+ }
128
+ }
129
+ }
130
+
131
+ // Client writing Keep/DataStore style authority smells
132
+ if (ctx === "Client") {
133
+ const smells = [
134
+ { re: /\bDataStoreService\b/, msg: "Client must not touch DataStoreService" },
135
+ { re: /Keep\.Server\.(Set|SetPersisted|Trade)/, msg: "Client must not mutate Keep.Server persistence APIs" },
136
+ ];
137
+ for (const smell of smells) {
138
+ const m = smell.re.exec(clean);
139
+ if (m) {
140
+ const { line, column } = lineColAt(clean, m.index);
141
+ out.push({
142
+ code: "CLUAU_AUTH003",
143
+ message: smell.msg,
144
+ line,
145
+ column,
146
+ severity: "error",
147
+ file: fileName,
148
+ });
149
+ }
150
+ }
151
+ }
152
+
153
+ return out;
154
+ }
155
+
156
+ export function assertAuthoritySafe(source: string, fileName: string, policy?: PlatformPolicy): void {
157
+ const errors = checkAuthority(source, fileName, policy).filter((d) => d.severity === "error");
158
+ if (!errors.length) return;
159
+ throw new Error(`Authority:\n${errors.map((e) => ` ${e.line}:${e.column} ${e.code} ${e.message}`).join("\n")}`);
160
+ }
161
+
162
+ export type { RunContext };
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Call-graph authority — transitive Client → banned Server paths via include graph.
3
+ * CLUAU_AUTH004 when a client/shared module reaches ServerScriptService/ServerStorage.
4
+ */
5
+
6
+ import { buildProjectGraph, type ProjectGraph } from "./project-graph.js";
7
+ import { runContextFromFileName } from "./roblox.js";
8
+ import type { PlatformDiagnostic } from "./diagnostics.js";
9
+ import { loadPlatformPolicy, type PlatformPolicy } from "./authority-check.js";
10
+
11
+ function pathLooksBanned(rel: string, banned: string[]): boolean {
12
+ const norm = rel.replace(/\\/g, "/");
13
+ return banned.some((b) => norm.includes(b));
14
+ }
15
+
16
+ function reachable(graph: ProjectGraph, from: string): Set<string> {
17
+ const adj = new Map<string, string[]>();
18
+ for (const n of graph.nodes) adj.set(n, []);
19
+ for (const e of graph.edges) {
20
+ if (!adj.has(e.from)) adj.set(e.from, []);
21
+ adj.get(e.from)!.push(e.to);
22
+ }
23
+ const seen = new Set<string>();
24
+ const stack = [from];
25
+ while (stack.length) {
26
+ const n = stack.pop()!;
27
+ if (seen.has(n)) continue;
28
+ seen.add(n);
29
+ for (const to of adj.get(n) || []) {
30
+ if (!seen.has(to)) stack.push(to);
31
+ }
32
+ }
33
+ seen.delete(from);
34
+ return seen;
35
+ }
36
+
37
+ export function checkAuthorityGraph(
38
+ projectRoot: string,
39
+ rootDir = "src",
40
+ policy: PlatformPolicy = loadPlatformPolicy(projectRoot),
41
+ ): PlatformDiagnostic[] {
42
+ const graph = buildProjectGraph(projectRoot, rootDir);
43
+ const out: PlatformDiagnostic[] = [];
44
+ const clientBanned = policy.rules?.clientCannotImport || ["ServerScriptService", "ServerStorage"];
45
+
46
+ for (const node of graph.nodes) {
47
+ const ctx = runContextFromFileName(node);
48
+ if (ctx !== "Client" && ctx !== "Shared") continue;
49
+
50
+ const banned = ctx === "Client" ? clientBanned : ["ServerScriptService", "ServerStorage"];
51
+
52
+ if (pathLooksBanned(node, banned)) {
53
+ out.push({
54
+ code: "CLUAU_AUTH004",
55
+ message: `Architecture call-graph: ${ctx} file lives under banned path (${node})`,
56
+ line: 1,
57
+ column: 1,
58
+ severity: "error",
59
+ file: node,
60
+ });
61
+ continue;
62
+ }
63
+
64
+ for (const dep of reachable(graph, node)) {
65
+ if (pathLooksBanned(dep, banned)) {
66
+ out.push({
67
+ code: "CLUAU_AUTH004",
68
+ message: `Architecture call-graph: ${ctx} \`${node}\` transitively reaches banned \`${dep}\``,
69
+ line: 1,
70
+ column: 1,
71
+ severity: "error",
72
+ file: node,
73
+ });
74
+ break;
75
+ }
76
+ }
77
+ }
78
+
79
+ return out;
80
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Incremental compile cache + bounded parallel job runner.
3
+ * Cache lives under `.cluaupp/compile-cache/` (gitignored via `.cluaupp/`).
4
+ */
5
+
6
+ import crypto from "node:crypto";
7
+ import fs from "node:fs";
8
+ import os from "node:os";
9
+ import path from "node:path";
10
+
11
+ export const COMPILE_CACHE_SCHEMA = 1 as const;
12
+
13
+ export interface CachedArtifact {
14
+ name: string;
15
+ contents: string;
16
+ }
17
+
18
+ export interface CachedJob {
19
+ rel: string;
20
+ sourceHash: string;
21
+ files: CachedArtifact[];
22
+ stale: string[];
23
+ }
24
+
25
+ export interface CompileCacheIndex {
26
+ schemaVersion: typeof COMPILE_CACHE_SCHEMA;
27
+ /** rel → sourceHash */
28
+ entries: Record<string, string>;
29
+ }
30
+
31
+ export function compileCacheDir(projectRoot: string): string {
32
+ return path.join(projectRoot, ".cluaupp", "compile-cache");
33
+ }
34
+
35
+ export function hashText(text: string): string {
36
+ return crypto.createHash("sha256").update(text, "utf8").digest("hex");
37
+ }
38
+
39
+ export function fingerprintSource(source: string, configKey: string): string {
40
+ return hashText(`${configKey}\n${source}`);
41
+ }
42
+
43
+ function indexPath(projectRoot: string): string {
44
+ return path.join(compileCacheDir(projectRoot), "index.json");
45
+ }
46
+
47
+ function jobPath(projectRoot: string, rel: string): string {
48
+ const safe = rel.replace(/[\\/]/g, "__");
49
+ return path.join(compileCacheDir(projectRoot), `${safe}.json`);
50
+ }
51
+
52
+ export function loadCompileCacheIndex(projectRoot: string): CompileCacheIndex {
53
+ const file = indexPath(projectRoot);
54
+ if (!fs.existsSync(file)) {
55
+ return { schemaVersion: COMPILE_CACHE_SCHEMA, entries: {} };
56
+ }
57
+ try {
58
+ const raw = JSON.parse(fs.readFileSync(file, "utf8")) as CompileCacheIndex;
59
+ if (!raw || typeof raw.entries !== "object") {
60
+ return { schemaVersion: COMPILE_CACHE_SCHEMA, entries: {} };
61
+ }
62
+ return { schemaVersion: COMPILE_CACHE_SCHEMA, entries: raw.entries };
63
+ } catch {
64
+ return { schemaVersion: COMPILE_CACHE_SCHEMA, entries: {} };
65
+ }
66
+ }
67
+
68
+ export function saveCompileCacheIndex(projectRoot: string, index: CompileCacheIndex): void {
69
+ const dir = compileCacheDir(projectRoot);
70
+ fs.mkdirSync(dir, { recursive: true });
71
+ fs.writeFileSync(indexPath(projectRoot), JSON.stringify(index, null, "\t") + "\n", "utf8");
72
+ }
73
+
74
+ export function readCachedJob(projectRoot: string, rel: string, sourceHash: string): CachedJob | null {
75
+ const index = loadCompileCacheIndex(projectRoot);
76
+ if (index.entries[rel] !== sourceHash) {
77
+ return null;
78
+ }
79
+ const file = jobPath(projectRoot, rel);
80
+ if (!fs.existsSync(file)) {
81
+ return null;
82
+ }
83
+ try {
84
+ const job = JSON.parse(fs.readFileSync(file, "utf8")) as CachedJob;
85
+ if (!job || job.sourceHash !== sourceHash || !Array.isArray(job.files)) {
86
+ return null;
87
+ }
88
+ return job;
89
+ } catch {
90
+ return null;
91
+ }
92
+ }
93
+
94
+ export function writeCachedJob(projectRoot: string, job: CachedJob): void {
95
+ const dir = compileCacheDir(projectRoot);
96
+ fs.mkdirSync(dir, { recursive: true });
97
+ fs.writeFileSync(jobPath(projectRoot, job.rel), JSON.stringify(job) + "\n", "utf8");
98
+ const index = loadCompileCacheIndex(projectRoot);
99
+ index.entries[job.rel] = job.sourceHash;
100
+ saveCompileCacheIndex(projectRoot, index);
101
+ }
102
+
103
+ /** Bounded concurrency over async work items. */
104
+ export async function mapPool<T, R>(
105
+ items: T[],
106
+ concurrency: number,
107
+ fn: (item: T, index: number) => Promise<R>,
108
+ ): Promise<R[]> {
109
+ const limit = Math.max(1, Math.min(concurrency, items.length || 1));
110
+ const results = new Array<R>(items.length);
111
+ let next = 0;
112
+ async function worker(): Promise<void> {
113
+ while (true) {
114
+ const i = next++;
115
+ if (i >= items.length) return;
116
+ results[i] = await fn(items[i], i);
117
+ }
118
+ }
119
+ await Promise.all(Array.from({ length: limit }, () => worker()));
120
+ return results;
121
+ }
122
+
123
+ export function defaultJobCount(): number {
124
+ const env = Number(process.env.CLUAUPP_JOBS || process.env.CLPP_JOBS || "");
125
+ if (Number.isFinite(env) && env > 0) {
126
+ return Math.floor(env);
127
+ }
128
+ return Math.max(1, Math.min(8, os.cpus()?.length || 4));
129
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Context Safety — capability profile for CL++ compile (Cluaupp target).
3
+ * CLPP may consume this JSON later; Cluaupp enforces rules today.
4
+ */
5
+
6
+ import type { RunContext } from "./roblox.js";
7
+
8
+ export const CAPABILITY_PROFILE_SCHEMA_VERSION = 1 as const;
9
+
10
+ export type CapabilityId =
11
+ | "client_player"
12
+ | "server_storage"
13
+ | "data_stores"
14
+ | "messaging"
15
+ | "fire_server"
16
+ | "fire_client";
17
+
18
+ export interface CapabilityRule {
19
+ id: string;
20
+ /** Human message for diagnostics */
21
+ message: string;
22
+ /** Regex source matched against comment-stripped CL++ lines */
23
+ pattern: string;
24
+ /** Contexts where this pattern is illegal */
25
+ forbiddenIn: RunContext[];
26
+ severity: "error" | "warning";
27
+ capability?: CapabilityId;
28
+ }
29
+
30
+ export interface CapabilityProfile {
31
+ schemaVersion: typeof CAPABILITY_PROFILE_SCHEMA_VERSION;
32
+ target: "roblox";
33
+ rules: CapabilityRule[];
34
+ }
35
+
36
+ /** Builtin Roblox platform capability rules (v1). */
37
+ export function builtinCapabilityProfile(): CapabilityProfile {
38
+ return {
39
+ schemaVersion: CAPABILITY_PROFILE_SCHEMA_VERSION,
40
+ target: "roblox",
41
+ rules: [
42
+ {
43
+ id: "CLUAUPP_CTX_LOCALPLAYER",
44
+ message: "`LocalPlayer` is client-only — illegal in Server scripts",
45
+ pattern: String.raw`\bLocalPlayer\b`,
46
+ forbiddenIn: ["Server"],
47
+ severity: "error",
48
+ capability: "client_player",
49
+ },
50
+ {
51
+ id: "CLUAUPP_CTX_LOCALPLAYER_SHARED",
52
+ message: "`LocalPlayer` in Shared modules is unsafe if required from the server — prefer Client scripts",
53
+ pattern: String.raw`\bLocalPlayer\b`,
54
+ forbiddenIn: ["Shared"],
55
+ severity: "warning",
56
+ capability: "client_player",
57
+ },
58
+ {
59
+ id: "CLUAUPP_CTX_DATASTORE",
60
+ message: "`DataStoreService` is server-only — illegal in Client scripts",
61
+ pattern: String.raw`\bDataStoreService\b|GetService\s*<\s*DataStoreService\s*>|GetService\s*\(\s*["']DataStoreService["']\s*\)`,
62
+ forbiddenIn: ["Client"],
63
+ severity: "error",
64
+ capability: "data_stores",
65
+ },
66
+ {
67
+ id: "CLUAUPP_CTX_MESSAGING",
68
+ message: "`MessagingService` is server-only — illegal in Client scripts",
69
+ pattern: String.raw`\bMessagingService\b|GetService\s*<\s*MessagingService\s*>`,
70
+ forbiddenIn: ["Client"],
71
+ severity: "error",
72
+ capability: "messaging",
73
+ },
74
+ {
75
+ id: "CLUAUPP_CTX_SERVERSTORAGE",
76
+ message: "`ServerStorage` is server-only — illegal in Client scripts",
77
+ pattern: String.raw`\bServerStorage\b|GetService\s*<\s*ServerStorage\s*>`,
78
+ forbiddenIn: ["Client"],
79
+ severity: "error",
80
+ capability: "server_storage",
81
+ },
82
+ {
83
+ id: "CLUAUPP_CTX_FIRE_SERVER",
84
+ message: "`FireServer` is client→server — illegal in Server scripts",
85
+ pattern: String.raw`\bFireServer\b`,
86
+ forbiddenIn: ["Server"],
87
+ severity: "error",
88
+ capability: "fire_server",
89
+ },
90
+ {
91
+ id: "CLUAUPP_CTX_FIRE_CLIENT",
92
+ message: "`FireClient` / `FireAllClients` are server→client — illegal in Client scripts",
93
+ pattern: String.raw`\bFireAllClients\b|\bFireClient\b`,
94
+ forbiddenIn: ["Client"],
95
+ severity: "error",
96
+ capability: "fire_client",
97
+ },
98
+ ],
99
+ };
100
+ }
101
+
102
+ export function capabilitiesForContext(context: RunContext): CapabilityId[] {
103
+ switch (context) {
104
+ case "Client":
105
+ return ["client_player", "fire_server"];
106
+ case "Server":
107
+ return ["server_storage", "data_stores", "messaging", "fire_client"];
108
+ case "Plugin":
109
+ return ["client_player", "server_storage", "data_stores", "messaging", "fire_server", "fire_client"];
110
+ case "Shared":
111
+ return [];
112
+ default:
113
+ return [];
114
+ }
115
+ }
@@ -0,0 +1,25 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { projectRoot } from "../package-info.js";
4
+ import { builtinCapabilityProfile, type CapabilityProfile } from "./capabilities.js";
5
+
6
+ export function capabilityProfilePath(): string {
7
+ return path.join(projectRoot, "api", "generated", "capability-profile.json");
8
+ }
9
+
10
+ export function writeCapabilityProfile(profile: CapabilityProfile = builtinCapabilityProfile()): string {
11
+ const out = capabilityProfilePath();
12
+ fs.mkdirSync(path.dirname(out), { recursive: true });
13
+ const tmp = `${out}.${process.pid}.tmp`;
14
+ fs.writeFileSync(tmp, `${JSON.stringify(profile, null, "\t")}\n`, "utf8");
15
+ fs.renameSync(tmp, out);
16
+ return out;
17
+ }
18
+
19
+ export function loadCapabilityProfile(): CapabilityProfile {
20
+ const file = capabilityProfilePath();
21
+ if (!fs.existsSync(file)) {
22
+ return builtinCapabilityProfile();
23
+ }
24
+ return JSON.parse(fs.readFileSync(file, "utf8")) as CapabilityProfile;
25
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * DataModel component contracts — validate expected children/classNames on graph nodes.
3
+ */
4
+
5
+ import type { DatamodelProfile } from "./datamodel.js";
6
+ import type { ComponentContract } from "./authority-check.js";
7
+ import type { PlatformDiagnostic } from "./diagnostics.js";
8
+
9
+ function isA(actual: string, expected: string): boolean {
10
+ if (actual === expected) return true;
11
+ // soft hierarchy aliases for v1
12
+ const basePart = new Set(["Part", "MeshPart", "UnionOperation", "BasePart", "SpawnLocation", "WedgePart", "CornerWedgePart", "TrussPart"]);
13
+ if (expected === "BasePart" && basePart.has(actual)) return true;
14
+ if (expected === "Instance") return true;
15
+ if (expected === "Folder" && (actual === "Folder" || actual === "Configuration")) return true;
16
+ return false;
17
+ }
18
+
19
+ export function checkComponentContracts(
20
+ profile: DatamodelProfile,
21
+ contracts: ComponentContract[],
22
+ ): PlatformDiagnostic[] {
23
+ const out: PlatformDiagnostic[] = [];
24
+ for (const contract of contracts) {
25
+ const matches = Object.values(profile.byPath).filter(
26
+ (n) => n.name === contract.path || n.path === contract.path || n.path.endsWith(`.${contract.path}`),
27
+ );
28
+ if (!matches.length) {
29
+ out.push({
30
+ code: "CLUAU_DM_CONTRACT_MISSING",
31
+ message: `Component contract "${contract.path}" — no matching Instance in Rojo DataModel graph`,
32
+ line: 1,
33
+ column: 1,
34
+ severity: "warning",
35
+ });
36
+ continue;
37
+ }
38
+ for (const node of matches) {
39
+ for (const [childName, expectedClass] of Object.entries(contract.children)) {
40
+ const child = node.children[childName];
41
+ if (!child) {
42
+ out.push({
43
+ code: "CLUAU_DM_CONTRACT_CHILD",
44
+ message: `Component ${node.path}: missing child "${childName}" (expected ${expectedClass})`,
45
+ line: 1,
46
+ column: 1,
47
+ severity: "error",
48
+ });
49
+ continue;
50
+ }
51
+ if (!isA(child.className, expectedClass)) {
52
+ out.push({
53
+ code: "CLUAU_DM_CONTRACT_TYPE",
54
+ message: `Component ${node.path}.${childName}: expected ${expectedClass}, got ${child.className}`,
55
+ line: 1,
56
+ column: 1,
57
+ severity: "error",
58
+ });
59
+ }
60
+ }
61
+ }
62
+ }
63
+ return out;
64
+ }