modwright 0.1.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 (455) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +212 -0
  3. package/assets/claude/agents/mw-explore.md +46 -0
  4. package/assets/claude/agents/mw-fixture.md +33 -0
  5. package/assets/claude/agents/mw-rule-author.md +51 -0
  6. package/assets/claude/agents/mw-scaffold-author.md +48 -0
  7. package/assets/claude/skills/mw-author.md +59 -0
  8. package/assets/claude/skills/mw-new-item.md +51 -0
  9. package/assets/claude/skills/mw-new-spell.md +50 -0
  10. package/assets/claude/skills/mw-scaffold.md +59 -0
  11. package/assets/claude/skills/mw-ship.md +57 -0
  12. package/assets/claude/skills/mw-validate.md +51 -0
  13. package/assets/claude/skills/mw-verify.md +59 -0
  14. package/assets/nivalisnights/extract_resources.py +177 -0
  15. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Config.json +5 -0
  16. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/BootstrapClient.lua +55 -0
  17. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/BootstrapServer.lua +81 -0
  18. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Log.lua +96 -0
  19. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Probes.lua +1038 -0
  20. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/ProbesClient.lua +74 -0
  21. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Protocol.lua +985 -0
  22. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Watch.lua +175 -0
  23. package/bridges/bg3-se/Mods/ModWrightBridge/meta.lsx +45 -0
  24. package/bridges/bg3-se/README.md +99 -0
  25. package/bridges/bg3-se/bridge.json +68 -0
  26. package/bridges/cp2077-cet/ModWrightBridge/init.lua +375 -0
  27. package/bridges/cp2077-cet/ModWrightBridge/probes.lua +1104 -0
  28. package/bridges/cp2077-cet/ModWrightBridge/protocol.lua +1306 -0
  29. package/bridges/cp2077-cet/README.md +165 -0
  30. package/bridges/cp2077-cet/bridge.json +53 -0
  31. package/dist/core/bridge/arm.js +175 -0
  32. package/dist/core/bridge/cet-console.js +26 -0
  33. package/dist/core/bridge/cet-log.js +362 -0
  34. package/dist/core/bridge/index.js +11 -0
  35. package/dist/core/bridge/locate.js +35 -0
  36. package/dist/core/bridge/paths.js +34 -0
  37. package/dist/core/bridge/pending.js +89 -0
  38. package/dist/core/bridge/poll.js +155 -0
  39. package/dist/core/bridge/stage.js +100 -0
  40. package/dist/core/bridge/status.js +97 -0
  41. package/dist/core/bridge/types.js +145 -0
  42. package/dist/core/bridge/validate.js +107 -0
  43. package/dist/core/build/index.js +6 -0
  44. package/dist/core/build/manifest.js +47 -0
  45. package/dist/core/build/paths.js +32 -0
  46. package/dist/core/build/run.js +342 -0
  47. package/dist/core/build/stage.js +265 -0
  48. package/dist/core/build/steps/bg3.js +129 -0
  49. package/dist/core/build/steps/core.js +195 -0
  50. package/dist/core/build/steps/cyberpunk.js +95 -0
  51. package/dist/core/build/steps/listing.js +35 -0
  52. package/dist/core/build/types.js +13 -0
  53. package/dist/core/build/zip.js +148 -0
  54. package/dist/core/compat/check.js +113 -0
  55. package/dist/core/compat/pe.js +220 -0
  56. package/dist/core/compat/types.js +1 -0
  57. package/dist/core/compat/version.js +70 -0
  58. package/dist/core/deploy/guards.js +292 -0
  59. package/dist/core/deploy/index.js +5 -0
  60. package/dist/core/deploy/plan.js +696 -0
  61. package/dist/core/deploy/reload-apply.js +361 -0
  62. package/dist/core/deploy/reload.js +358 -0
  63. package/dist/core/deploy/sources.js +169 -0
  64. package/dist/core/deploy/types.js +1 -0
  65. package/dist/core/epic.js +54 -0
  66. package/dist/core/fsutil.js +191 -0
  67. package/dist/core/home.js +70 -0
  68. package/dist/core/knowledge/facts.js +229 -0
  69. package/dist/core/knowledge/index.js +4 -0
  70. package/dist/core/knowledge/search.js +112 -0
  71. package/dist/core/knowledge/store.js +123 -0
  72. package/dist/core/knowledge/types.js +1 -0
  73. package/dist/core/ledger/check.js +42 -0
  74. package/dist/core/ledger/hooks.js +80 -0
  75. package/dist/core/ledger/index.js +7 -0
  76. package/dist/core/ledger/query.js +17 -0
  77. package/dist/core/ledger/record.js +84 -0
  78. package/dist/core/ledger/status.js +53 -0
  79. package/dist/core/ledger/store.js +98 -0
  80. package/dist/core/ledger/types.js +108 -0
  81. package/dist/core/logs/index.js +4 -0
  82. package/dist/core/logs/registry.js +50 -0
  83. package/dist/core/logs/scan.js +195 -0
  84. package/dist/core/logs/triage.js +160 -0
  85. package/dist/core/logs/types.js +1 -0
  86. package/dist/core/lsx.js +10 -0
  87. package/dist/core/lz4.js +66 -0
  88. package/dist/core/mods.js +229 -0
  89. package/dist/core/process.js +49 -0
  90. package/dist/core/project/build-steps.js +83 -0
  91. package/dist/core/project/describe.js +74 -0
  92. package/dist/core/project/index.js +5 -0
  93. package/dist/core/project/load.js +146 -0
  94. package/dist/core/project/schema.js +264 -0
  95. package/dist/core/project/types.js +3 -0
  96. package/dist/core/prompts.js +72 -0
  97. package/dist/core/registry.js +35 -0
  98. package/dist/core/safety/backup.js +206 -0
  99. package/dist/core/safety/execute.js +188 -0
  100. package/dist/core/safety/index.js +5 -0
  101. package/dist/core/safety/plan.js +122 -0
  102. package/dist/core/safety/restore.js +172 -0
  103. package/dist/core/safety/types.js +1 -0
  104. package/dist/core/scaffold/bootstrap.js +7 -0
  105. package/dist/core/scaffold/catalog.js +28 -0
  106. package/dist/core/scaffold/claude-assets.js +271 -0
  107. package/dist/core/scaffold/collisions.js +64 -0
  108. package/dist/core/scaffold/emit.js +356 -0
  109. package/dist/core/scaffold/engine.js +680 -0
  110. package/dist/core/scaffold/ids.js +78 -0
  111. package/dist/core/scaffold/index.js +10 -0
  112. package/dist/core/scaffold/jsonpath.js +121 -0
  113. package/dist/core/scaffold/load.js +81 -0
  114. package/dist/core/scaffold/render.js +85 -0
  115. package/dist/core/scaffold/sequence.js +396 -0
  116. package/dist/core/scaffold/types.js +309 -0
  117. package/dist/core/steam.js +63 -0
  118. package/dist/core/surfaceutil.js +36 -0
  119. package/dist/core/testplan/check.js +67 -0
  120. package/dist/core/testplan/index.js +5 -0
  121. package/dist/core/testplan/load.js +100 -0
  122. package/dist/core/testplan/registry.js +317 -0
  123. package/dist/core/testplan/run.js +389 -0
  124. package/dist/core/testplan/types.js +82 -0
  125. package/dist/core/text/converters/bg3.js +50 -0
  126. package/dist/core/text/converters/cyberpunk.js +36 -0
  127. package/dist/core/text/converters/index.js +261 -0
  128. package/dist/core/text/diff/cr2w.js +180 -0
  129. package/dist/core/text/diff/index.js +16 -0
  130. package/dist/core/text/diff/loca.js +43 -0
  131. package/dist/core/text/diff/lsx.js +126 -0
  132. package/dist/core/text/handles.js +86 -0
  133. package/dist/core/text/index.js +6 -0
  134. package/dist/core/text/json.js +46 -0
  135. package/dist/core/text/templates/cache.js +71 -0
  136. package/dist/core/text/templates/index.js +3 -0
  137. package/dist/core/text/templates/library.js +245 -0
  138. package/dist/core/text/templates/types.js +51 -0
  139. package/dist/core/text/toolchain.js +16 -0
  140. package/dist/core/text/types.js +1 -0
  141. package/dist/core/text/validate.js +149 -0
  142. package/dist/core/text/xml.js +58 -0
  143. package/dist/core/toolchain/datafetch.js +151 -0
  144. package/dist/core/toolchain/env.js +63 -0
  145. package/dist/core/toolchain/features.js +170 -0
  146. package/dist/core/toolchain/index.js +8 -0
  147. package/dist/core/toolchain/install.js +191 -0
  148. package/dist/core/toolchain/locate.js +275 -0
  149. package/dist/core/toolchain/remember.js +84 -0
  150. package/dist/core/toolchain/run.js +318 -0
  151. package/dist/core/toolchain/specs.js +351 -0
  152. package/dist/core/toolchain/types.js +1 -0
  153. package/dist/core/types.js +1 -0
  154. package/dist/core/userconfig.js +66 -0
  155. package/dist/core/validate/context.js +139 -0
  156. package/dist/core/validate/index.js +5 -0
  157. package/dist/core/validate/registry.js +58 -0
  158. package/dist/core/validate/run.js +131 -0
  159. package/dist/core/validate/suppressions.js +91 -0
  160. package/dist/core/validate/types.js +2 -0
  161. package/dist/core/yamledit.js +51 -0
  162. package/dist/index.js +389 -0
  163. package/dist/server.js +1914 -0
  164. package/dist/surfaces/baldursgate3/compat.js +164 -0
  165. package/dist/surfaces/baldursgate3/identity.js +110 -0
  166. package/dist/surfaces/baldursgate3/index/build.js +331 -0
  167. package/dist/surfaces/baldursgate3/index/index.js +17 -0
  168. package/dist/surfaces/baldursgate3/index/info.js +40 -0
  169. package/dist/surfaces/baldursgate3/index/parsers/loca.js +41 -0
  170. package/dist/surfaces/baldursgate3/index/parsers/lsx.js +137 -0
  171. package/dist/surfaces/baldursgate3/index/parsers/stats.js +45 -0
  172. package/dist/surfaces/baldursgate3/index/parsers/treasuretable.js +57 -0
  173. package/dist/surfaces/baldursgate3/index/parsers/xmlutil.js +142 -0
  174. package/dist/surfaces/baldursgate3/index/queries.js +327 -0
  175. package/dist/surfaces/baldursgate3/index/schema.js +62 -0
  176. package/dist/surfaces/baldursgate3/index.js +3 -0
  177. package/dist/surfaces/baldursgate3/logs.js +278 -0
  178. package/dist/surfaces/baldursgate3/modsettings.js +115 -0
  179. package/dist/surfaces/baldursgate3/pak.js +153 -0
  180. package/dist/surfaces/baldursgate3/settings.js +49 -0
  181. package/dist/surfaces/baldursgate3/surface.js +232 -0
  182. package/dist/surfaces/baldursgate3/validators/bridge.js +7 -0
  183. package/dist/surfaces/baldursgate3/validators/files.js +476 -0
  184. package/dist/surfaces/baldursgate3/validators/items.js +1001 -0
  185. package/dist/surfaces/baldursgate3/validators/progression.js +734 -0
  186. package/dist/surfaces/baldursgate3/validators/stats.js +1076 -0
  187. package/dist/surfaces/baldursgate3/validators/story.js +790 -0
  188. package/dist/surfaces/baldursgate3/validators/templates.js +7 -0
  189. package/dist/surfaces/cyberpunk2077/compat.js +223 -0
  190. package/dist/surfaces/cyberpunk2077/index/build.js +231 -0
  191. package/dist/surfaces/cyberpunk2077/index/index.js +17 -0
  192. package/dist/surfaces/cyberpunk2077/index/info.js +51 -0
  193. package/dist/surfaces/cyberpunk2077/index/model.js +52 -0
  194. package/dist/surfaces/cyberpunk2077/index/parsers/tweak.js +483 -0
  195. package/dist/surfaces/cyberpunk2077/index/parsers/tweakxl-yaml.js +183 -0
  196. package/dist/surfaces/cyberpunk2077/index/queries.js +504 -0
  197. package/dist/surfaces/cyberpunk2077/index/resource.js +135 -0
  198. package/dist/surfaces/cyberpunk2077/index/schema.js +90 -0
  199. package/dist/surfaces/cyberpunk2077/index.js +3 -0
  200. package/dist/surfaces/cyberpunk2077/logs.js +742 -0
  201. package/dist/surfaces/cyberpunk2077/paths.js +12 -0
  202. package/dist/surfaces/cyberpunk2077/surface.js +218 -0
  203. package/dist/surfaces/cyberpunk2077/validators/bridge.js +7 -0
  204. package/dist/surfaces/cyberpunk2077/validators/cr2w.js +349 -0
  205. package/dist/surfaces/cyberpunk2077/validators/garments.js +1181 -0
  206. package/dist/surfaces/cyberpunk2077/validators/mesh.js +338 -0
  207. package/dist/surfaces/cyberpunk2077/validators/modsettings.js +120 -0
  208. package/dist/surfaces/cyberpunk2077/validators/packaging.js +463 -0
  209. package/dist/surfaces/cyberpunk2077/validators/red4ext.js +318 -0
  210. package/dist/surfaces/cyberpunk2077/validators/redscript.js +193 -0
  211. package/dist/surfaces/cyberpunk2077/validators/templates.js +7 -0
  212. package/dist/surfaces/cyberpunk2077/validators/tweaks.js +768 -0
  213. package/dist/surfaces/cyberpunk2077/validators/vehicles.js +531 -0
  214. package/dist/surfaces/cyberpunk2077/validators/xl.js +308 -0
  215. package/dist/surfaces/eldenring/binders.js +111 -0
  216. package/dist/surfaces/eldenring/compat.js +166 -0
  217. package/dist/surfaces/eldenring/dcx.js +49 -0
  218. package/dist/surfaces/eldenring/index.js +13 -0
  219. package/dist/surfaces/eldenring/loaders.js +132 -0
  220. package/dist/surfaces/eldenring/logs.js +67 -0
  221. package/dist/surfaces/eldenring/paramdef.js +256 -0
  222. package/dist/surfaces/eldenring/params.js +129 -0
  223. package/dist/surfaces/eldenring/surface.js +121 -0
  224. package/dist/surfaces/eldenring/tools.js +52 -0
  225. package/dist/surfaces/eldenring/validators/binders.js +193 -0
  226. package/dist/surfaces/eldenring/validators/packages.js +159 -0
  227. package/dist/surfaces/eldenring/validators/params.js +334 -0
  228. package/dist/surfaces/eldenring/validators/profiles.js +287 -0
  229. package/dist/surfaces/index.js +34 -0
  230. package/dist/surfaces/nivalisnights/compat.js +229 -0
  231. package/dist/surfaces/nivalisnights/index/build.js +310 -0
  232. package/dist/surfaces/nivalisnights/index/index.js +5 -0
  233. package/dist/surfaces/nivalisnights/index/queries.js +231 -0
  234. package/dist/surfaces/nivalisnights/index.js +4 -0
  235. package/dist/surfaces/nivalisnights/logs.js +473 -0
  236. package/dist/surfaces/nivalisnights/surface.js +195 -0
  237. package/dist/surfaces/skyrimse/archives.js +229 -0
  238. package/dist/surfaces/skyrimse/compat.js +163 -0
  239. package/dist/surfaces/skyrimse/index.js +12 -0
  240. package/dist/surfaces/skyrimse/loadorder.js +174 -0
  241. package/dist/surfaces/skyrimse/logs.js +241 -0
  242. package/dist/surfaces/skyrimse/plugins.js +149 -0
  243. package/dist/surfaces/skyrimse/records.js +94 -0
  244. package/dist/surfaces/skyrimse/surface.js +240 -0
  245. package/dist/surfaces/skyrimse/validators/archive-records.js +165 -0
  246. package/dist/surfaces/skyrimse/validators/archives.js +110 -0
  247. package/dist/surfaces/skyrimse/validators/plugins.js +374 -0
  248. package/dist/surfaces/skyrimse/validators/records.js +117 -0
  249. package/dist/surfaces/stardewvalley/compat.js +190 -0
  250. package/dist/surfaces/stardewvalley/config.js +42 -0
  251. package/dist/surfaces/stardewvalley/contentpatcher.js +203 -0
  252. package/dist/surfaces/stardewvalley/index/index.js +229 -0
  253. package/dist/surfaces/stardewvalley/index/wiki.js +190 -0
  254. package/dist/surfaces/stardewvalley/index.js +12 -0
  255. package/dist/surfaces/stardewvalley/logs.js +307 -0
  256. package/dist/surfaces/stardewvalley/mods.js +182 -0
  257. package/dist/surfaces/stardewvalley/surface.js +244 -0
  258. package/dist/surfaces/stardewvalley/validators/content.js +321 -0
  259. package/dist/surfaces/stardewvalley/validators/fields.js +147 -0
  260. package/dist/surfaces/stardewvalley/validators/loadorder.js +69 -0
  261. package/dist/surfaces/stardewvalley/validators/manifest.js +221 -0
  262. package/dist/surfaces/subnautica2/compat.js +138 -0
  263. package/dist/surfaces/subnautica2/index.js +5 -0
  264. package/dist/surfaces/subnautica2/logs.js +129 -0
  265. package/dist/surfaces/subnautica2/paks.js +173 -0
  266. package/dist/surfaces/subnautica2/surface.js +151 -0
  267. package/dist/surfaces/subnautica2/validators/packaging.js +298 -0
  268. package/dist/surfaces/valheim/compat.js +171 -0
  269. package/dist/surfaces/valheim/index/index.js +301 -0
  270. package/dist/surfaces/valheim/index/tables.js +103 -0
  271. package/dist/surfaces/valheim/index.js +11 -0
  272. package/dist/surfaces/valheim/logs.js +197 -0
  273. package/dist/surfaces/valheim/packaging.js +131 -0
  274. package/dist/surfaces/valheim/plugins.js +270 -0
  275. package/dist/surfaces/valheim/references.js +121 -0
  276. package/dist/surfaces/valheim/surface.js +223 -0
  277. package/dist/surfaces/valheim/validators/packaging.js +218 -0
  278. package/dist/surfaces/valheim/validators/plugins.js +287 -0
  279. package/dist/surfaces/valheim/validators/references.js +169 -0
  280. package/knowledge/baldursgate3/animations.gr2.yaml +166 -0
  281. package/knowledge/baldursgate3/audio.voice.yaml +182 -0
  282. package/knowledge/baldursgate3/audio.wwise.yaml +104 -0
  283. package/knowledge/baldursgate3/charactercreation.heads-hair.yaml +193 -0
  284. package/knowledge/baldursgate3/communitylibrary.shared-content.yaml +129 -0
  285. package/knowledge/baldursgate3/compatibilityframework.api.yaml +176 -0
  286. package/knowledge/baldursgate3/crash.causes.yaml +90 -0
  287. package/knowledge/baldursgate3/dialogue.dialogbank.yaml +120 -0
  288. package/knowledge/baldursgate3/dialogue.files.yaml +236 -0
  289. package/knowledge/baldursgate3/dialogue.osiris-goals.yaml +386 -0
  290. package/knowledge/baldursgate3/dialogue.yaml +122 -0
  291. package/knowledge/baldursgate3/feats.classes.yaml +164 -0
  292. package/knowledge/baldursgate3/items.cloth-physics.yaml +167 -0
  293. package/knowledge/baldursgate3/items.roottemplates.yaml +320 -0
  294. package/knowledge/baldursgate3/items.visualbank.yaml +628 -0
  295. package/knowledge/baldursgate3/journal.quests.yaml +214 -0
  296. package/knowledge/baldursgate3/loadorder.modsettings.yaml +128 -0
  297. package/knowledge/baldursgate3/localization.handles.yaml +92 -0
  298. package/knowledge/baldursgate3/misc.encoding-and-practice.yaml +111 -0
  299. package/knowledge/baldursgate3/origins.companions.yaml +144 -0
  300. package/knowledge/baldursgate3/osiris.signatures.yaml +211 -0
  301. package/knowledge/baldursgate3/packaging.meta.yaml +155 -0
  302. package/knowledge/baldursgate3/packaging.paks.yaml +79 -0
  303. package/knowledge/baldursgate3/process.launch.yaml +34 -0
  304. package/knowledge/baldursgate3/progressions.yaml +92 -0
  305. package/knowledge/baldursgate3/publishing.modio-nexus.yaml +170 -0
  306. package/knowledge/baldursgate3/publishing.toolkit-modio.yaml +135 -0
  307. package/knowledge/baldursgate3/races.progression.yaml +228 -0
  308. package/knowledge/baldursgate3/scriptextender.console.yaml +257 -0
  309. package/knowledge/baldursgate3/scriptextender.yaml +252 -0
  310. package/knowledge/baldursgate3/stats.boosts.yaml +293 -0
  311. package/knowledge/baldursgate3/stats.equipment-kits.yaml +69 -0
  312. package/knowledge/baldursgate3/stats.interrupts.yaml +45 -0
  313. package/knowledge/baldursgate3/stats.itemcombos.yaml +65 -0
  314. package/knowledge/baldursgate3/stats.spells.yaml +293 -0
  315. package/knowledge/baldursgate3/stats.weapons.yaml +210 -0
  316. package/knowledge/baldursgate3/toolchain.divine.yaml +179 -0
  317. package/knowledge/baldursgate3/toolchain.lslib-formats.yaml +242 -0
  318. package/knowledge/baldursgate3/toolkit.levels.yaml +177 -0
  319. package/knowledge/baldursgate3/ui.icons-atlas.yaml +78 -0
  320. package/knowledge/baldursgate3/ui.icons.yaml +412 -0
  321. package/knowledge/baldursgate3/ui.mcm.yaml +161 -0
  322. package/knowledge/baldursgate3/vfx.materials.yaml +174 -0
  323. package/knowledge/baldursgate3/visuals.characterfix.yaml +73 -0
  324. package/knowledge/cyberpunk2077/animations.anims.yaml +118 -0
  325. package/knowledge/cyberpunk2077/anims.archivexl.yaml +163 -0
  326. package/knowledge/cyberpunk2077/anims.authoring.yaml +579 -0
  327. package/knowledge/cyberpunk2077/anims.encoder.yaml +156 -0
  328. package/knowledge/cyberpunk2077/anims.graph.yaml +629 -0
  329. package/knowledge/cyberpunk2077/anims.method.yaml +360 -0
  330. package/knowledge/cyberpunk2077/anims.pipeline.yaml +352 -0
  331. package/knowledge/cyberpunk2077/anims.roundtrip.yaml +222 -0
  332. package/knowledge/cyberpunk2077/anims.sets.yaml +176 -0
  333. package/knowledge/cyberpunk2077/archivexl.bodytypes.yaml +149 -0
  334. package/knowledge/cyberpunk2077/archivexl.dynamic-appearances.yaml +218 -0
  335. package/knowledge/cyberpunk2077/archivexl.manifest.yaml +214 -0
  336. package/knowledge/cyberpunk2077/audio.events.yaml +168 -0
  337. package/knowledge/cyberpunk2077/audio.sounds.yaml +164 -0
  338. package/knowledge/cyberpunk2077/audio.voicesets.yaml +146 -0
  339. package/knowledge/cyberpunk2077/cet.sandbox.yaml +219 -0
  340. package/knowledge/cyberpunk2077/clothing.garments.yaml +283 -0
  341. package/knowledge/cyberpunk2077/clothing.refits.yaml +215 -0
  342. package/knowledge/cyberpunk2077/codeware.overview.yaml +111 -0
  343. package/knowledge/cyberpunk2077/codeware.systems.yaml +192 -0
  344. package/knowledge/cyberpunk2077/drones.combat.yaml +416 -0
  345. package/knowledge/cyberpunk2077/drones.control.yaml +552 -0
  346. package/knowledge/cyberpunk2077/entities.appearance.yaml +166 -0
  347. package/knowledge/cyberpunk2077/equipmentex.slots.yaml +151 -0
  348. package/knowledge/cyberpunk2077/fx.effects.yaml +246 -0
  349. package/knowledge/cyberpunk2077/fx.lights.yaml +171 -0
  350. package/knowledge/cyberpunk2077/hair.modding.yaml +145 -0
  351. package/knowledge/cyberpunk2077/items.wiring.yaml +246 -0
  352. package/knowledge/cyberpunk2077/loadorder.archives.yaml +71 -0
  353. package/knowledge/cyberpunk2077/mesh.blender.yaml +209 -0
  354. package/knowledge/cyberpunk2077/nativedb.dump.yaml +98 -0
  355. package/knowledge/cyberpunk2077/npc.appearance.yaml +275 -0
  356. package/knowledge/cyberpunk2077/npc.behaviour.yaml +128 -0
  357. package/knowledge/cyberpunk2077/npc.locomotion.yaml +490 -0
  358. package/knowledge/cyberpunk2077/packaging.deploy.yaml +175 -0
  359. package/knowledge/cyberpunk2077/packaging.variants.yaml +176 -0
  360. package/knowledge/cyberpunk2077/player.control.yaml +918 -0
  361. package/knowledge/cyberpunk2077/process.assetbuild.yaml +154 -0
  362. package/knowledge/cyberpunk2077/process.lessons.yaml +276 -0
  363. package/knowledge/cyberpunk2077/quests.minor.yaml +46 -0
  364. package/knowledge/cyberpunk2077/quests.scenes.yaml +282 -0
  365. package/knowledge/cyberpunk2077/quickhacks.tiers.yaml +41 -0
  366. package/knowledge/cyberpunk2077/red4ext.api.yaml +645 -0
  367. package/knowledge/cyberpunk2077/red4ext.plugins.yaml +124 -0
  368. package/knowledge/cyberpunk2077/redhottools.hotreload.yaml +245 -0
  369. package/knowledge/cyberpunk2077/redscript.compile-log.yaml +76 -0
  370. package/knowledge/cyberpunk2077/redscript.modules.yaml +118 -0
  371. package/knowledge/cyberpunk2077/toolchain.wolvenkit.yaml +163 -0
  372. package/knowledge/cyberpunk2077/tweakdb.loot-recipes.yaml +184 -0
  373. package/knowledge/cyberpunk2077/tweakdb.melee.yaml +352 -0
  374. package/knowledge/cyberpunk2077/tweakdb.weapons.yaml +325 -0
  375. package/knowledge/cyberpunk2077/tweakxl.hotreload.yaml +151 -0
  376. package/knowledge/cyberpunk2077/tweakxl.yaml-syntax.yaml +208 -0
  377. package/knowledge/cyberpunk2077/ui.icons.yaml +259 -0
  378. package/knowledge/cyberpunk2077/ui.inkatlas.yaml +81 -0
  379. package/knowledge/cyberpunk2077/ui.modsettings.yaml +204 -0
  380. package/knowledge/cyberpunk2077/vehicles.control.yaml +475 -0
  381. package/knowledge/cyberpunk2077/vehicles.dashboard-ui.yaml +203 -0
  382. package/knowledge/cyberpunk2077/vehicles.entities.yaml +195 -0
  383. package/knowledge/cyberpunk2077/vehicles.wiring.yaml +137 -0
  384. package/knowledge/cyberpunk2077/wolvenkit.cli.yaml +167 -0
  385. package/knowledge/cyberpunk2077/workspot.system.yaml +55 -0
  386. package/knowledge/cyberpunk2077/world.interactions.yaml +41 -0
  387. package/knowledge/cyberpunk2077/world.loot-container.yaml +167 -0
  388. package/knowledge/cyberpunk2077/world.sectors.yaml +233 -0
  389. package/knowledge/eldenring/formats.binders.yaml +237 -0
  390. package/knowledge/eldenring/launcher.me3.yaml +230 -0
  391. package/knowledge/eldenring/launcher.modengine2.yaml +119 -0
  392. package/knowledge/eldenring/params.paramdef.yaml +149 -0
  393. package/knowledge/eldenring/params.regulation.yaml +142 -0
  394. package/knowledge/eldenring/saves.online.yaml +118 -0
  395. package/knowledge/eldenring/toolchain.cli.yaml +256 -0
  396. package/knowledge/nivalisnights/modding.runtime.yaml +157 -0
  397. package/knowledge/skyrimse/archives.bsa.yaml +212 -0
  398. package/knowledge/skyrimse/install.detection.yaml +41 -0
  399. package/knowledge/skyrimse/loadorder.files.yaml +242 -0
  400. package/knowledge/skyrimse/plugins.esl.yaml +189 -0
  401. package/knowledge/skyrimse/plugins.format.yaml +224 -0
  402. package/knowledge/skyrimse/skse.runtime.yaml +283 -0
  403. package/knowledge/skyrimse/toolchain.xedit-spriggit.yaml +336 -0
  404. package/knowledge/stardewvalley/contentpatcher.format.yaml +338 -0
  405. package/knowledge/stardewvalley/distribution.channels.yaml +236 -0
  406. package/knowledge/stardewvalley/game.versions.yaml +202 -0
  407. package/knowledge/stardewvalley/install.layout.yaml +277 -0
  408. package/knowledge/stardewvalley/smapi.loader.yaml +284 -0
  409. package/knowledge/stardewvalley/smapi.logs.yaml +329 -0
  410. package/knowledge/stardewvalley/smapi.manifest.yaml +293 -0
  411. package/knowledge/stardewvalley/toolchain.build.yaml +261 -0
  412. package/knowledge/stardewvalley/wiki.dataformat.yaml +130 -0
  413. package/knowledge/subnautica2/game.build.yaml +331 -0
  414. package/knowledge/subnautica2/modding.state.yaml +210 -0
  415. package/knowledge/subnautica2/ue5.containers.yaml +72 -0
  416. package/knowledge/subnautica2/ue5.paks.yaml +203 -0
  417. package/knowledge/subnautica2/ue5.tooling.yaml +175 -0
  418. package/knowledge/subnautica2/ue5.ue4ss.yaml +203 -0
  419. package/knowledge/valheim/bepinex.loader.yaml +247 -0
  420. package/knowledge/valheim/bepinex.plugins.yaml +251 -0
  421. package/knowledge/valheim/game.versions.yaml +321 -0
  422. package/knowledge/valheim/install.layout.yaml +216 -0
  423. package/knowledge/valheim/jotunn.library.yaml +334 -0
  424. package/knowledge/valheim/thunderstore.packaging.yaml +217 -0
  425. package/knowledge/valheim/toolchain.build.yaml +111 -0
  426. package/package.json +29 -0
  427. package/scaffolds/baldursgate3/new-class-injection.yaml +148 -0
  428. package/scaffolds/baldursgate3/new-item.yaml +264 -0
  429. package/scaffolds/baldursgate3/new-mod.yaml +174 -0
  430. package/scaffolds/baldursgate3/new-passive.yaml +100 -0
  431. package/scaffolds/baldursgate3/new-project.yaml +118 -0
  432. package/scaffolds/baldursgate3/new-quest-stub.yaml +159 -0
  433. package/scaffolds/baldursgate3/new-spell.yaml +102 -0
  434. package/scaffolds/baldursgate3/new-status.yaml +119 -0
  435. package/scaffolds/baldursgate3/sequences/subclass-kit.yaml +50 -0
  436. package/scaffolds/cyberpunk2077/edit-tweak.yaml +70 -0
  437. package/scaffolds/cyberpunk2077/mesh-export-preset.yaml +170 -0
  438. package/scaffolds/cyberpunk2077/new-entity-patch.yaml +255 -0
  439. package/scaffolds/cyberpunk2077/new-garment-refit.yaml +279 -0
  440. package/scaffolds/cyberpunk2077/new-item-chain.yaml +548 -0
  441. package/scaffolds/cyberpunk2077/new-localization.yaml +130 -0
  442. package/scaffolds/cyberpunk2077/new-mod-settings.yaml +133 -0
  443. package/scaffolds/cyberpunk2077/new-player-replacer.yaml +1003 -0
  444. package/scaffolds/cyberpunk2077/new-project.yaml +157 -0
  445. package/scaffolds/cyberpunk2077/new-sector-node.yaml +156 -0
  446. package/scaffolds/cyberpunk2077/new-tweak.yaml +121 -0
  447. package/scaffolds/cyberpunk2077/new-vehicle-livery.yaml +148 -0
  448. package/scaffolds/cyberpunk2077/new-workspot-entity.yaml +177 -0
  449. package/scaffolds/cyberpunk2077/sequences/iconic-weapon.yaml +72 -0
  450. package/scaffolds/cyberpunk2077/sequences/quickhack-takeover.yaml +213 -0
  451. package/scaffolds/stardewvalley/new-cp-patch.yaml +106 -0
  452. package/scaffolds/stardewvalley/new-csharp-mod.yaml +195 -0
  453. package/scaffolds/stardewvalley/new-project.yaml +184 -0
  454. package/scaffolds/stardewvalley/sequences/content-pack.yaml +31 -0
  455. package/scripts/mw-tool.mjs +184 -0
@@ -0,0 +1,358 @@
1
+ import * as path from "node:path";
2
+ import { promises as fs } from "node:fs";
3
+ import { globToRegExp } from "../validate/index.js";
4
+ const CP2077_TABLE = [
5
+ {
6
+ match: "r6/tweaks/**",
7
+ action: "hot",
8
+ via: 'TweakXL.Reload() via cp2077.lua.eval (verified to re-apply a changed flat)',
9
+ caveat: "The .hot-tweaks sentinel is consumed by RHT but did NOT drive a reload in RHT 1.3.0.33690 (verified fact " +
10
+ "hot-tweaks-consumed-without-reload-1.3.0), so the reliable path is TweakXL.Reload() through the bridge. " +
11
+ "Reload adds and changes records but does not unload a record whose file was removed (verified fact " +
12
+ "reload-does-not-unload-removed-records): after deleting a tweak file the record stays in TweakDB until restart. " +
13
+ "Read-back: a cp2077.tweakdb.flat probe on the changed flat (TweakXL.log's \"Import completed.\" line after the reload corroborates).",
14
+ },
15
+ {
16
+ match: "r6/scripts/**",
17
+ action: "hot",
18
+ via: "the .hot-scripts sentinel (RHT ScriptWatcher deletes it and runs ScriptLoader::ReloadScripts())",
19
+ caveat: "Diff-aware: downgrades to save-reload when the diff adds a ScriptableSystem request handler, adds a struct field or " +
20
+ "retypes one — struct fields are not reinitialised and a new handler needs a session reload. deploy runs the scc precheck first; " +
21
+ "a compile error surfaces as an RHT message box on the game window. Read-back: a cp2077.lua.eval of <Mod>Dev.BuildStamp(). " +
22
+ "Without Red Hot Tools present and watching this launch, a .reds is inert until a restart (redscript compiles at launch).",
23
+ },
24
+ {
25
+ match: "archive/pc/mod/*.archive",
26
+ action: "hot",
27
+ via: "the archive/pc/hot folder (RHT ArchiveWatcher unloads, moves it into archive/pc/mod, reloads, invalidates the cache, calls ArchiveXL.Reload())",
28
+ caveat: "Non-mesh archives only: an archive carrying a mounted mesh stays cold and needs a restart (knowledge fact " +
29
+ "cp2077.process.assetbuild.rht-hot-folder-banned-for-whole-mesh-archives). Already-spawned instances keep the old resource — pair " +
30
+ "with a despawn/respawn or unequip/re-equip. Read-back: \"Archives reload completed\" in RedHotTools.log. " +
31
+ "Without Red Hot Tools present and watching this launch, the .archive is locked while the game runs; restart.",
32
+ },
33
+ {
34
+ match: "archive/pc/mod/*.xl",
35
+ action: "hot",
36
+ via: 'ArchiveXL.Reload() via cp2077.lua.eval, or the RHT overlay "Reload extensions"',
37
+ caveat: "Read-back: ArchiveXL.log's discovery lines. An .archive dropped alongside already reloads extensions; this row is the .xl-only case.",
38
+ },
39
+ {
40
+ match: "bin/x64/plugins/cyber_engine_tweaks/mods/**",
41
+ action: "hot",
42
+ via: 'the CET overlay\'s "Reload all mods" button',
43
+ caveat: "Manual: CET's registered globals have no reload entry and there is no autoexec (verified negative in CET source), so this cannot " +
44
+ "be triggered from a file. For the mod's own dev loop, scaffold a dofile-based module reload, kept author-only.",
45
+ },
46
+ {
47
+ match: "red4ext/plugins/**",
48
+ action: "restart",
49
+ caveat: "Version-locked to the game build; no hot path.",
50
+ },
51
+ {
52
+ match: "r6/input/**",
53
+ action: "restart",
54
+ caveat: "Input Loader merges its XML into the input contexts at launch; no reload path is known.",
55
+ },
56
+ ];
57
+ const BG3_TABLE = [
58
+ {
59
+ match: "Mods/*/ScriptExtender/Lua/**",
60
+ action: "hot",
61
+ via: "Ext.Debug.Reset(true, false) via bg3.lua.eval (needs DeveloperMode), which fires ResetCompleted",
62
+ caveat: "Hot only when the SE Lua is loose under Data/Mods/<Folder> (the wiki's reset scope); Lua that ships only inside the pak needs a " +
63
+ "restart until a hot-overrides layer has mirrored it loose, because the pak is locked for the process lifetime. Reset " +
64
+ "reloads the Lua state but may degrade Osiris; a save reload is the clean fix. Read-back: the bridge's " +
65
+ "ResetCompleted handler increments resetCount in the heartbeat.",
66
+ },
67
+ {
68
+ match: "Public/*/Stats/Generated/**",
69
+ action: "restart",
70
+ via: "Ext.Stats.Get(name):SetRawAttribute(...):Sync() per changed entry (session-mirrored)",
71
+ caveat: "No API reloads the .txt from disk. Until a stats mirror sends the equivalent Ext.Stats sync through bg3.lua.eval, restart. " +
72
+ "A functor-only change stays unknown until tested in game (StatsFunctors unsupported as of v29). The file still ships in the next pak.",
73
+ },
74
+ {
75
+ match: "Public/*/RootTemplates/**",
76
+ action: "restart",
77
+ caveat: "RootTemplates are read at session load only (no runtime bank reload); a restart is needed for existing and new instances alike.",
78
+ },
79
+ {
80
+ match: "**/*.lsx",
81
+ action: "save-reload",
82
+ caveat: "Progressions/ClassDescriptions changes apply to newly created instances; existing saves keep old data. Ext.StaticData.SyncResourceBank " +
83
+ "exists but its live effect is unverified (restart until it is tested in game). A respec makes that test cheap.",
84
+ },
85
+ { match: "**/*.pak", action: "restart", caveat: "Locked for the process lifetime, at the menu and after deactivation (verified 2026-09-13); a full exit is the only release." },
86
+ { match: "Mods/*/Localization/**", action: "restart", caveat: ".loca is read at load." },
87
+ ];
88
+ const STARDEW_TABLE = [
89
+ {
90
+ match: "Mods/**/manifest.json",
91
+ action: "restart",
92
+ caveat: "SMAPI reads every manifest once at launch (ModScanner); a new, renamed or re-versioned mod is not seen until then.",
93
+ },
94
+ {
95
+ match: "Mods/**/config.json",
96
+ action: "restart",
97
+ caveat: "A mod's config.json is read when the mod loads; the player guide says to edit it with the game closed 'or your changes might not take effect'. A Content Patcher pack's config values are tokens, and `patch reload` does not reload config schema.",
98
+ },
99
+ {
100
+ match: "Mods/**/*.dll",
101
+ action: "restart",
102
+ caveat: "A loaded assembly cannot be replaced; SMAPI loads C# mods once at launch.",
103
+ },
104
+ {
105
+ match: "Mods/**/i18n/*.json",
106
+ action: "hot",
107
+ via: "reload_i18n in the SMAPI console",
108
+ caveat: "SMAPI's own command: \"Reload translation files for all mods\", with the wiki's own note that if a mod caches the text \"it may show the old version until it updates\".",
109
+ },
110
+ {
111
+ match: "Mods/**/content.json",
112
+ action: "hot",
113
+ via: 'patch reload "<pack id>" in the SMAPI console',
114
+ caveat: "Content Patcher's command re-reads the pack's patches; its own guide says non-patch content like ConfigSchema and DynamicTokens is not reloaded, so a change to those is a restart. ModWright never sends the command.",
115
+ },
116
+ {
117
+ match: "Mods/**/assets/**",
118
+ action: "hot",
119
+ via: 'patch reload "<pack id>" in the SMAPI console',
120
+ caveat: "The files a pack's patches load; the reload re-applies the patches that name them. A file no patch names yet needs its patch first, and an asset a C# mod loads for itself is that mod's business, so restart to be sure.",
121
+ },
122
+ {
123
+ match: "Mods/**/*.json",
124
+ action: "hot",
125
+ via: 'patch reload "<pack id>" in the SMAPI console',
126
+ caveat: "Only for a JSON file a Content Patcher pack includes (an Include patch's FromFile); a JSON a C# mod reads for itself is that mod's business, so restart to be sure.",
127
+ },
128
+ ];
129
+ const SURFACE_TABLES = {
130
+ cyberpunk2077: CP2077_TABLE,
131
+ baldursgate3: BG3_TABLE,
132
+ stardewvalley: STARDEW_TABLE,
133
+ };
134
+ const RED_HOT_TOOLS_SURFACE = "cyberpunk2077";
135
+ const RHT_DEPENDENT_ROWS = new Set(["archive/pc/mod/*.archive", "r6/scripts/**"]);
136
+ const RANK = {
137
+ hot: 0,
138
+ "hot-degraded": 1,
139
+ "save-reload": 2,
140
+ restart: 3,
141
+ };
142
+ const UNKNOWN_NOTE = "not classified — restart to be sure";
143
+ function toPosix(p) {
144
+ return p.split(path.sep).join("/");
145
+ }
146
+ function matchesPath(glob, absPath) {
147
+ const re = globToRegExp(glob);
148
+ const segments = toPosix(absPath).split("/").filter((s) => s.length > 0);
149
+ for (let i = 0; i < segments.length; i++) {
150
+ if (re.test(segments.slice(i).join("/")))
151
+ return true;
152
+ }
153
+ return false;
154
+ }
155
+ function firstMatch(rules, absPath) {
156
+ return rules.find((rule) => matchesPath(rule.match, absPath));
157
+ }
158
+ export function redHotToolsDir(install) {
159
+ return path.join(install.root, "red4ext", "plugins", "RedHotTools");
160
+ }
161
+ export const ARCHIVE_WATCHER_LINE = "[ArchiveWatcher] Watching";
162
+ export const REAL_FS_FOR_APPLY = {
163
+ pathExists: async (p) => {
164
+ try {
165
+ await fs.access(p);
166
+ return true;
167
+ }
168
+ catch {
169
+ return false;
170
+ }
171
+ },
172
+ readDir: (p) => fs.readdir(p),
173
+ readFile: (p) => fs.readFile(p, "utf8"),
174
+ mtimeMs: async (p) => (await fs.stat(p)).mtimeMs,
175
+ };
176
+ const REAL_FS = REAL_FS_FOR_APPLY;
177
+ export async function detectRedHotTools(install, compat, deps = REAL_FS) {
178
+ const version = compat?.components.find((c) => c.id === "redhottools")?.version;
179
+ if (!install) {
180
+ return { present: false, watching: false, hotAvailable: false, version, reason: "no install was supplied, so Red Hot Tools presence could not be checked" };
181
+ }
182
+ const dir = redHotToolsDir(install);
183
+ const dllPath = path.join(dir, "RedHotTools.dll");
184
+ const present = await deps.pathExists(dllPath);
185
+ if (!present) {
186
+ return { present: false, watching: false, hotAvailable: false, version, dllPath, reason: `Red Hot Tools is not installed (${dllPath} not found)` };
187
+ }
188
+ let logPath;
189
+ try {
190
+ const entries = (await deps.readDir(dir)).filter((n) => /^RedHotTools.*\.log$/i.test(n));
191
+ let newestMtime = -1;
192
+ for (const name of entries) {
193
+ const full = path.join(dir, name);
194
+ const m = await deps.mtimeMs(full).catch(() => -1);
195
+ if (m > newestMtime) {
196
+ newestMtime = m;
197
+ logPath = full;
198
+ }
199
+ }
200
+ }
201
+ catch {
202
+ }
203
+ if (!logPath) {
204
+ return { present: true, watching: false, hotAvailable: false, version, dllPath, reason: "Red Hot Tools is installed but no RedHotTools*.log was found — launch the game once so the ArchiveWatcher arms" };
205
+ }
206
+ const log = await deps.readFile(logPath).catch(() => "");
207
+ const watching = log.includes(ARCHIVE_WATCHER_LINE);
208
+ return {
209
+ present: true,
210
+ watching,
211
+ hotAvailable: watching,
212
+ version,
213
+ dllPath,
214
+ logPath,
215
+ reason: watching
216
+ ? `Red Hot Tools ${version ?? "(version unknown)"} is installed and the ArchiveWatcher is watching (newest log ${path.basename(logPath)})`
217
+ : `Red Hot Tools is installed but the newest log (${path.basename(logPath)}) has no "${ARCHIVE_WATCHER_LINE}" line — the watcher is not armed this launch`,
218
+ };
219
+ }
220
+ export function redscriptChangeAction(before, after) {
221
+ if (before === undefined) {
222
+ return { action: "hot", reason: "a newly added script has no prior version to diff; the reload rebinds it" };
223
+ }
224
+ const addsRequestHandler = hasNewRequestHandler(before, after);
225
+ if (addsRequestHandler) {
226
+ return { action: "save-reload", reason: "the diff adds a ScriptableSystem request handler, which is not registered on a script reload (needs a session reload)" };
227
+ }
228
+ const structField = addsOrRetypesStructField(before, after);
229
+ if (structField) {
230
+ return { action: "save-reload", reason: structField };
231
+ }
232
+ return { action: "hot", reason: "no struct-field or request-handler change; the reload rebinds value-holder props of unchanged type" };
233
+ }
234
+ function hasNewRequestHandler(before, after) {
235
+ const re = /func\s+On[A-Za-z0-9_]*\s*\([^)]*:\s*ref<[A-Za-z0-9_]*Request>\s*\)/g;
236
+ const beforeSet = new Set(before.match(re) ?? []);
237
+ for (const m of after.match(re) ?? []) {
238
+ if (!beforeSet.has(m))
239
+ return true;
240
+ }
241
+ return false;
242
+ }
243
+ function addsOrRetypesStructField(before, after) {
244
+ const beforeStructs = structFields(before);
245
+ const afterStructs = structFields(after);
246
+ for (const [name, afterFields] of afterStructs) {
247
+ const beforeFields = beforeStructs.get(name);
248
+ if (!beforeFields)
249
+ continue;
250
+ for (const [field, type] of afterFields) {
251
+ const prev = beforeFields.get(field);
252
+ if (prev === undefined)
253
+ return `the diff adds field "${field}" to struct ${name}; struct fields are not reinitialised on a script reload`;
254
+ if (prev !== type)
255
+ return `the diff retypes field "${field}" of struct ${name} (${prev} -> ${type}); a live instance would carry the old layout`;
256
+ }
257
+ }
258
+ return undefined;
259
+ }
260
+ function structFields(text) {
261
+ const out = new Map();
262
+ const structRe = /struct\s+([A-Za-z0-9_]+)\s*\{([^}]*)\}/g;
263
+ let m;
264
+ while ((m = structRe.exec(text)) !== null) {
265
+ const name = m[1];
266
+ const body = m[2];
267
+ const fields = new Map();
268
+ const fieldRe = /let\s+([A-Za-z0-9_]+)\s*:\s*([^;=]+)\s*[;=]/g;
269
+ let f;
270
+ while ((f = fieldRe.exec(body)) !== null) {
271
+ fields.set(f[1], f[2].trim());
272
+ }
273
+ out.set(name, fields);
274
+ }
275
+ return out;
276
+ }
277
+ export function classifyReload(surfaceId, project, writtenPaths, options = {}) {
278
+ const projectRules = project.reload?.rules ?? [];
279
+ const table = SURFACE_TABLES[surfaceId];
280
+ const rht = options.redHotTools;
281
+ const scriptDiffs = options.scriptDiffs ?? {};
282
+ const classifications = writtenPaths.map((p) => {
283
+ const projectRule = firstMatch(projectRules, p);
284
+ if (projectRule) {
285
+ return {
286
+ path: p,
287
+ action: projectRule.action,
288
+ via: projectRule.via,
289
+ caveat: projectRule.caveat,
290
+ source: "project-rule",
291
+ matched: projectRule.match,
292
+ };
293
+ }
294
+ const row = table ? firstMatch(table, p) : undefined;
295
+ if (!row) {
296
+ return { path: p, action: "unknown", caveat: UNKNOWN_NOTE, source: "unmatched" };
297
+ }
298
+ if (surfaceId === RED_HOT_TOOLS_SURFACE && RHT_DEPENDENT_ROWS.has(row.match) && !rht?.hotAvailable) {
299
+ return {
300
+ path: p,
301
+ action: "restart",
302
+ via: undefined,
303
+ caveat: `${row.caveat ?? ""} Red Hot Tools' hot path is unavailable (${rht?.reason ?? "no presence check was run"}), so this needs a restart.`.trim(),
304
+ source: "red-hot-tools",
305
+ matched: row.match,
306
+ };
307
+ }
308
+ if (surfaceId === RED_HOT_TOOLS_SURFACE && row.match === "r6/scripts/**" && scriptDiffs[p] && scriptDiffs[p] !== "hot") {
309
+ return {
310
+ path: p,
311
+ action: scriptDiffs[p],
312
+ via: undefined,
313
+ caveat: `${row.caveat ?? ""} This diff cannot hot-reload cleanly, so it is downgraded to ${scriptDiffs[p]}.`.trim(),
314
+ source: "red-hot-tools",
315
+ matched: row.match,
316
+ };
317
+ }
318
+ return { path: p, action: row.action, via: row.via, caveat: row.caveat, source: "surface-table", matched: row.match };
319
+ });
320
+ const groups = [];
321
+ for (const c of classifications) {
322
+ const existing = groups.find((g) => g.action === c.action && g.via === c.via && g.caveat === c.caveat);
323
+ if (existing)
324
+ existing.paths.push(c.path);
325
+ else
326
+ groups.push({ action: c.action, via: c.via, caveat: c.caveat, paths: [c.path] });
327
+ }
328
+ const unclassified = classifications.filter((c) => c.action === "unknown").map((c) => c.path);
329
+ const ranked = classifications
330
+ .filter((c) => c.action !== "unknown")
331
+ .map((c) => RANK[c.action]);
332
+ if (unclassified.length > 0)
333
+ ranked.push(RANK.restart);
334
+ const overall = ranked.length === 0
335
+ ? "unknown"
336
+ : Object.keys(RANK).find((a) => RANK[a] === Math.max(...ranked));
337
+ const notes = [];
338
+ if (writtenPaths.length === 0) {
339
+ notes.push("nothing was written, so there is nothing to reload");
340
+ }
341
+ else if (!table && projectRules.length === 0) {
342
+ notes.push(`no reload table exists for "${surfaceId}" and the project declares no reload.rules — every path is ${UNKNOWN_NOTE}`);
343
+ }
344
+ if (unclassified.length > 0) {
345
+ notes.push(`${unclassified.length} path(s) ${UNKNOWN_NOTE}`);
346
+ }
347
+ if (rht)
348
+ notes.push(rht.reason);
349
+ notes.push("The never-launch / never-reload rule is retired in favour of the capability policy; a hot reload is confirmed by its read-back, not assumed.");
350
+ return {
351
+ overall,
352
+ groups,
353
+ unclassified,
354
+ classifications,
355
+ note: notes.join(". "),
356
+ redHotTools: rht ?? (surfaceId === RED_HOT_TOOLS_SURFACE ? { present: false, watching: false, hotAvailable: false, reason: "no Red Hot Tools presence check was run", version: options.compat?.components.find((c) => c.id === "redhottools")?.version } : undefined),
357
+ };
358
+ }
@@ -0,0 +1,169 @@
1
+ import * as path from "node:path";
2
+ import { pathExists } from "../fsutil.js";
3
+ import { normalizeBuildSteps } from "../project/build-steps.js";
4
+ import { resolveOutDirPath, resolveSourceOrStagePath, stageStepOutputDir } from "../build/paths.js";
5
+ import { looksLikePath } from "../build/steps/core.js";
6
+ import { normalizeForCompare } from "./guards.js";
7
+ const PACK_STEPS = new Set(["wolvenkit-pack", "divine-create-package", "zip"]);
8
+ function isWithin(parent, child) {
9
+ const rel = path.relative(normalizeForCompare(parent), normalizeForCompare(child));
10
+ return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
11
+ }
12
+ function same(a, b) {
13
+ return normalizeForCompare(a) === normalizeForCompare(b);
14
+ }
15
+ function stepLabel(step, index) {
16
+ return step.id ? `${step.step} (${step.id})` : `${step.step}#${index}`;
17
+ }
18
+ function allSteps(project) {
19
+ let steps;
20
+ try {
21
+ steps = normalizeBuildSteps(project.project);
22
+ }
23
+ catch {
24
+ return [];
25
+ }
26
+ const variants = Object.values(project.project.build?.variants ?? {});
27
+ const addSources = variants.flatMap((v) => v.addSources ?? []);
28
+ if (addSources.length > 0) {
29
+ steps = steps.map((s) => (s.step === "stage" ? { ...s, sources: [...s.sources, ...addSources] } : s));
30
+ }
31
+ return [...steps, ...variants.flatMap((v) => v.addSteps ?? [])];
32
+ }
33
+ async function execInputs(step, outputs, ctx) {
34
+ if (step.inputs && step.inputs.length > 0)
35
+ return step.inputs.map((p) => resolveSourceOrStagePath(p, ctx));
36
+ const found = [];
37
+ for (const raw of step.args) {
38
+ if (!looksLikePath(raw))
39
+ continue;
40
+ const abs = resolveSourceOrStagePath(raw, ctx);
41
+ if (!isWithin(ctx.projectRoot, abs))
42
+ continue;
43
+ if (outputs.some((o) => isWithin(abs, o)))
44
+ continue;
45
+ if (!raw.includes("${stage}") && !(await pathExists(abs)))
46
+ continue;
47
+ found.push(abs);
48
+ }
49
+ return found;
50
+ }
51
+ function stageInputs(sources, project, ctx) {
52
+ const out = [];
53
+ for (const entry of sources) {
54
+ if (typeof entry !== "string" && entry.output !== undefined) {
55
+ out.push(resolveOutDirPath(entry.output, ctx));
56
+ continue;
57
+ }
58
+ const id = typeof entry === "string" ? entry : entry.source;
59
+ const source = project.sources.find((s) => s.id === id);
60
+ if (source)
61
+ out.push(source.absPath);
62
+ }
63
+ return out;
64
+ }
65
+ export async function buildSourceGraph(project, outDirAbs) {
66
+ const stageRoot = path.join(project.root, ".modwright", "cache", "build", "<deploy-trace>");
67
+ const ctx = {
68
+ projectRoot: project.root,
69
+ stageRoot,
70
+ outDirAbs: outDirAbs ?? project.build?.absOutDir ?? project.root,
71
+ version: project.project.version ?? "0.0.0",
72
+ };
73
+ const nodes = [];
74
+ const steps = allSteps(project);
75
+ for (let index = 0; index < steps.length; index++) {
76
+ const step = steps[index];
77
+ const label = stepLabel(step, index);
78
+ let outputs = [];
79
+ let inputs = [];
80
+ switch (step.step) {
81
+ case "stage":
82
+ outputs = [stageStepOutputDir(stageRoot, step.id ?? `stage-${index}`, step.into)];
83
+ inputs = stageInputs(step.sources, project, ctx);
84
+ break;
85
+ case "wolvenkit-pack":
86
+ case "divine-create-package":
87
+ outputs = [resolveOutDirPath(step.out, ctx)];
88
+ inputs = [resolveSourceOrStagePath(step.from, ctx)];
89
+ break;
90
+ case "zip":
91
+ outputs = [resolveOutDirPath(step.name, ctx)];
92
+ inputs = [resolveSourceOrStagePath(step.from, ctx)];
93
+ break;
94
+ case "blender-export":
95
+ outputs = [resolveSourceOrStagePath(step.out, ctx)];
96
+ inputs = [resolveSourceOrStagePath(step.blend, ctx), resolveSourceOrStagePath(step.script, ctx)];
97
+ break;
98
+ case "wolvenkit-import":
99
+ outputs = [resolveSourceOrStagePath(step.outDir, ctx)];
100
+ inputs = [resolveSourceOrStagePath(step.glb, ctx), resolveSourceOrStagePath(step.base, ctx)];
101
+ break;
102
+ case "exec":
103
+ outputs = step.expectOutputs.map((p) => resolveSourceOrStagePath(p, ctx));
104
+ inputs = await execInputs(step, outputs, ctx);
105
+ break;
106
+ default:
107
+ continue;
108
+ }
109
+ nodes.push({ label, kind: step.step, outputs, inputs });
110
+ }
111
+ return { project, stageRoot, nodes };
112
+ }
113
+ function trace(graph, producers) {
114
+ const leaves = new Map();
115
+ const visited = new Set(producers);
116
+ const queue = [...producers];
117
+ while (queue.length > 0) {
118
+ const node = queue.shift();
119
+ for (const input of node.inputs) {
120
+ if (!isWithin(graph.stageRoot, input))
121
+ leaves.set(normalizeForCompare(input), input);
122
+ for (const other of graph.nodes) {
123
+ if (visited.has(other))
124
+ continue;
125
+ if (other.outputs.some((o) => same(o, input) || isWithin(input, o) || isWithin(o, input))) {
126
+ visited.add(other);
127
+ queue.push(other);
128
+ }
129
+ }
130
+ }
131
+ }
132
+ return [...leaves.values()];
133
+ }
134
+ function projectSources(project) {
135
+ return project.sources.filter((s) => s.exists).map((s) => s.absPath);
136
+ }
137
+ export function artifactSources(graph, builtArtifact, options = {}) {
138
+ if (options.fromSourcePath) {
139
+ return { paths: [options.fromSourcePath], via: "the project source it deploys", known: true };
140
+ }
141
+ const producers = graph.nodes.filter((n) => n.outputs.some((o) => same(o, builtArtifact) || isWithin(o, builtArtifact)));
142
+ if (producers.length > 0) {
143
+ const labels = producers.map((p) => p.label).join(", ");
144
+ const leaves = trace(graph, producers);
145
+ if (leaves.length > 0) {
146
+ return { paths: leaves, via: `the inputs of the build step that produces it (${labels})`, known: true };
147
+ }
148
+ if (producers.some((p) => PACK_STEPS.has(p.kind))) {
149
+ return { paths: projectSources(graph.project), via: `the project's own sources (${labels} packs them; no narrower input traced)`, known: true };
150
+ }
151
+ return { paths: [], via: `nothing traceable: ${labels} declares no input inside the project`, known: false };
152
+ }
153
+ const recorded = options.recorded;
154
+ if (recorded) {
155
+ const label = recorded.id ? `${recorded.step} (${recorded.id})` : recorded.step;
156
+ if (recorded.inputs.length > 0) {
157
+ return { paths: [...new Set(recorded.inputs)], via: `the inputs the build manifest records for ${label}`, known: true };
158
+ }
159
+ if (PACK_STEPS.has(recorded.step)) {
160
+ return { paths: projectSources(graph.project), via: `the project's own sources (${label} packs them)`, known: true };
161
+ }
162
+ return { paths: [], via: `nothing traceable: the build manifest records no input for ${label}`, known: false };
163
+ }
164
+ const container = graph.project.sources.find((s) => s.exists && isWithin(s.absPath, builtArtifact));
165
+ if (container) {
166
+ return { paths: [container.absPath], via: `the project source that contains it (${container.id})`, known: true };
167
+ }
168
+ return { paths: [], via: "nothing traceable: no declared build step produces it", known: false };
169
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,54 @@
1
+ import { promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ import { pathExists } from "./fsutil.js";
4
+ import { readRegistryValue } from "./registry.js";
5
+ export const EPIC_LAUNCHER_REGISTRY_KEYS = [String.raw `HKLM\Software\Epic Games\EpicGamesLauncher`, String.raw `HKLM\Software\WOW6432Node\Epic Games\EpicGamesLauncher`];
6
+ export const EPIC_LAUNCHER_REGISTRY_VALUE = "AppDataPath";
7
+ export async function epicManifestsDir() {
8
+ const override = process.env.MODWRIGHT_EPIC_MANIFESTS;
9
+ if (override && (await pathExists(override)))
10
+ return override;
11
+ const fromRegistry = await readRegistryValue(EPIC_LAUNCHER_REGISTRY_KEYS, EPIC_LAUNCHER_REGISTRY_VALUE);
12
+ if (fromRegistry?.value)
13
+ return path.join(fromRegistry.value, "Manifests");
14
+ const programData = process.env.ProgramData ?? process.env.PROGRAMDATA;
15
+ if (!programData)
16
+ return undefined;
17
+ return path.join(programData, "Epic", "EpicGamesLauncher", "Data", "Manifests");
18
+ }
19
+ export async function readEpicManifests(dir) {
20
+ let names;
21
+ try {
22
+ names = (await fs.readdir(dir)).filter((n) => n.toLowerCase().endsWith(".item")).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
23
+ }
24
+ catch {
25
+ return [];
26
+ }
27
+ const out = [];
28
+ for (const name of names) {
29
+ const file = path.join(dir, name);
30
+ try {
31
+ const doc = JSON.parse(await fs.readFile(file, "utf8"));
32
+ if (typeof doc.AppName !== "string" || typeof doc.InstallLocation !== "string")
33
+ continue;
34
+ out.push({ file, appName: doc.AppName, installLocation: doc.InstallLocation, ...(typeof doc.DisplayName === "string" ? { displayName: doc.DisplayName } : {}) });
35
+ }
36
+ catch {
37
+ }
38
+ }
39
+ return out;
40
+ }
41
+ export async function findInEpic(appNames, dir) {
42
+ const manifestsDir = dir ?? (await epicManifestsDir());
43
+ if (!manifestsDir || !(await pathExists(manifestsDir)))
44
+ return [];
45
+ const wanted = new Set(appNames.map((a) => a.toLowerCase()));
46
+ const roots = [];
47
+ for (const m of await readEpicManifests(manifestsDir)) {
48
+ if (!m.installLocation)
49
+ continue;
50
+ if (wanted.has(m.appName.toLowerCase()) && !roots.includes(m.installLocation))
51
+ roots.push(m.installLocation);
52
+ }
53
+ return roots;
54
+ }