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,86 @@
1
+ import { cloneJson, isJsonObject, walkJson } from "./json.js";
2
+ const RENUMBERED_KEYS = ["HandleId", "BufferId"];
3
+ export function renumberHandles(root) {
4
+ const maps = {
5
+ HandleId: new Map(),
6
+ BufferId: new Map(),
7
+ };
8
+ const danglingRefs = [];
9
+ walkJson(root, (node, jsonPath) => {
10
+ if (!isJsonObject(node))
11
+ return;
12
+ const ref = node.HandleRefId;
13
+ if (typeof ref === "string") {
14
+ const target = maps.HandleId.get(ref);
15
+ if (target)
16
+ target.paths.push(`${jsonPath}.HandleRefId`);
17
+ else
18
+ danglingRefs.push({ ref, path: `${jsonPath}.HandleRefId` });
19
+ }
20
+ for (const key of RENUMBERED_KEYS) {
21
+ const raw = node[key];
22
+ if (typeof raw !== "string")
23
+ continue;
24
+ const map = maps[key];
25
+ const existing = map.get(raw);
26
+ if (existing) {
27
+ existing.paths.push(`${jsonPath}.${key}`);
28
+ }
29
+ else {
30
+ map.set(raw, { to: String(map.size), paths: [`${jsonPath}.${key}`] });
31
+ }
32
+ }
33
+ });
34
+ const cloned = cloneJson(root);
35
+ const dangling = new Set(danglingRefs.map((d) => d.path));
36
+ walkJson(cloned, (node, jsonPath) => {
37
+ if (!isJsonObject(node))
38
+ return;
39
+ for (const key of RENUMBERED_KEYS) {
40
+ const raw = node[key];
41
+ if (typeof raw !== "string")
42
+ continue;
43
+ const mapped = maps[key].get(raw);
44
+ if (mapped)
45
+ node[key] = mapped.to;
46
+ }
47
+ const ref = node.HandleRefId;
48
+ if (typeof ref === "string" && !dangling.has(`${jsonPath}.HandleRefId`)) {
49
+ const mapped = maps.HandleId.get(ref);
50
+ if (mapped)
51
+ node.HandleRefId = mapped.to;
52
+ }
53
+ });
54
+ const renumbered = [];
55
+ for (const key of RENUMBERED_KEYS) {
56
+ for (const [from, { to, paths }] of maps[key]) {
57
+ if (from !== to)
58
+ renumbered.push({ key, from, to, paths });
59
+ }
60
+ }
61
+ return { root: cloned, renumbered, danglingRefs };
62
+ }
63
+ export function handlesAreInEncounterOrder(root) {
64
+ const seen = { HandleId: new Map(), BufferId: new Map() };
65
+ let ok = true;
66
+ walkJson(root, (node) => {
67
+ if (!ok || !isJsonObject(node))
68
+ return;
69
+ for (const key of RENUMBERED_KEYS) {
70
+ const raw = node[key];
71
+ if (typeof raw !== "string")
72
+ continue;
73
+ const map = seen[key];
74
+ const already = map.get(raw);
75
+ if (already !== undefined)
76
+ continue;
77
+ const expected = String(map.size);
78
+ if (raw !== expected) {
79
+ ok = false;
80
+ return;
81
+ }
82
+ map.set(raw, map.size);
83
+ }
84
+ });
85
+ return ok;
86
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./types.js";
2
+ export { defaultLocateTool } from "./toolchain.js";
3
+ export { renumberHandles, handlesAreInEncounterOrder } from "./handles.js";
4
+ export { convert, getConverter, CONVERTERS, BG3_CONVERTERS, CYBERPUNK_CONVERTERS } from "./converters/index.js";
5
+ export { fieldDiff, diffCr2wJson, diffCr2wJsonText, diffLsx, diffLocaXml, diffLocaEntries, parseLocaXml } from "./diff/index.js";
6
+ export * from "./templates/index.js";
@@ -0,0 +1,46 @@
1
+ export function isJsonObject(v) {
2
+ return v !== null && typeof v === "object" && !Array.isArray(v);
3
+ }
4
+ export function isJsonArray(v) {
5
+ return Array.isArray(v);
6
+ }
7
+ export function parseJson(text, label) {
8
+ try {
9
+ return JSON.parse(text);
10
+ }
11
+ catch (err) {
12
+ throw new Error(`${label}: not valid JSON (${err.message})`);
13
+ }
14
+ }
15
+ export function walkJson(root, visit, jsonPath = "$") {
16
+ visit(root, jsonPath);
17
+ if (isJsonArray(root)) {
18
+ root.forEach((item, i) => walkJson(item, visit, `${jsonPath}[${i}]`));
19
+ }
20
+ else if (isJsonObject(root)) {
21
+ for (const [key, value] of Object.entries(root)) {
22
+ walkJson(value, visit, `${jsonPath}.${key}`);
23
+ }
24
+ }
25
+ }
26
+ export function dollarValue(v) {
27
+ if (isJsonObject(v) && "$value" in v)
28
+ return v.$value;
29
+ return v;
30
+ }
31
+ export function dollarType(v) {
32
+ if (isJsonObject(v) && typeof v.$type === "string")
33
+ return v.$type;
34
+ return undefined;
35
+ }
36
+ export function cloneJson(v) {
37
+ if (isJsonArray(v))
38
+ return v.map((x) => cloneJson(x));
39
+ if (isJsonObject(v)) {
40
+ const out = {};
41
+ for (const [k, val] of Object.entries(v))
42
+ out[k] = cloneJson(val);
43
+ return out;
44
+ }
45
+ return v;
46
+ }
@@ -0,0 +1,71 @@
1
+ import { createHash } from "node:crypto";
2
+ import { promises as fs } from "node:fs";
3
+ import * as path from "node:path";
4
+ export function computeTemplateCacheKey(input) {
5
+ const parts = [
6
+ input.surface,
7
+ input.gameVersion,
8
+ `${input.toolId}@${input.toolVersion}`,
9
+ input.container,
10
+ input.internalPath,
11
+ input.conversion ?? "",
12
+ ];
13
+ return createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 32);
14
+ }
15
+ const SAFE_SEGMENT = /^(?!\.{1,2}$)[A-Za-z0-9._-]+$/;
16
+ export function templateCacheDir(projectRoot, surface, gameVersion, key) {
17
+ for (const [label, value] of [["surface", surface], ["gameVersion", gameVersion], ["key", key]]) {
18
+ if (!SAFE_SEGMENT.test(value)) {
19
+ throw new Error(`template ${label} must be a single name of letters, digits, ".", "_" or "-", got ${JSON.stringify(value)}`);
20
+ }
21
+ }
22
+ return path.join(projectRoot, ".modwright", "cache", "templates", surface, gameVersion, key);
23
+ }
24
+ export function templateContentFilename(sourcePath, convertedExt) {
25
+ const ext = convertedExt ?? path.extname(sourcePath) ?? ".bin";
26
+ const normalized = ext.startsWith(".") ? ext : `.${ext}`;
27
+ return `content${normalized === "." ? ".bin" : normalized}`;
28
+ }
29
+ export async function checkTemplateInvalidation(record, opts) {
30
+ const reasons = [];
31
+ if (opts.currentGameVersion !== undefined && opts.currentGameVersion !== record.gameVersion) {
32
+ reasons.push(`the install reports game version "${opts.currentGameVersion}", but this cache entry is keyed to "${record.gameVersion}"`);
33
+ }
34
+ if (opts.locateTool) {
35
+ const location = await opts.locateTool(record.toolId);
36
+ if (!(location.found && location.versionNotProbed)) {
37
+ const currentToolVersion = location.found ? (location.version ?? "unknown") : "not found";
38
+ if (currentToolVersion !== record.toolVersion) {
39
+ reasons.push(`${record.toolId}'s version is now "${currentToolVersion}", but this entry was extracted with "${record.toolVersion}"`);
40
+ }
41
+ }
42
+ }
43
+ if (record.sourceContainer) {
44
+ try {
45
+ const stat = await fs.stat(record.sourceContainer);
46
+ if (record.containerSize !== undefined && stat.size !== record.containerSize) {
47
+ reasons.push(`container "${record.sourceContainer}" is now ${stat.size} bytes, this entry recorded ${record.containerSize}`);
48
+ }
49
+ if (record.containerMtime !== undefined) {
50
+ const mtime = stat.mtime.toISOString();
51
+ if (mtime !== record.containerMtime) {
52
+ reasons.push(`container "${record.sourceContainer}" mtime is now ${mtime}, this entry recorded ${record.containerMtime}`);
53
+ }
54
+ }
55
+ }
56
+ catch {
57
+ reasons.push(`container "${record.sourceContainer}" could not be read (missing, or no longer accessible)`);
58
+ }
59
+ }
60
+ try {
61
+ const content = await fs.readFile(opts.contentAbsPath);
62
+ const sha256 = createHash("sha256").update(content).digest("hex");
63
+ if (sha256 !== record.sha256) {
64
+ reasons.push(`the cached content's sha256 (${sha256}) does not match the record's (${record.sha256}) — a corrupted or hand-edited entry`);
65
+ }
66
+ }
67
+ catch {
68
+ reasons.push(`the cached content file "${opts.contentAbsPath}" is missing or unreadable`);
69
+ }
70
+ return { valid: reasons.length === 0, reasons };
71
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./types.js";
2
+ export { computeTemplateCacheKey, templateCacheDir, templateContentFilename, checkTemplateInvalidation } from "./cache.js";
3
+ export { extractTemplate, listTemplates, getTemplate, invalidateTemplate } from "./library.js";
@@ -0,0 +1,245 @@
1
+ import { createHash } from "node:crypto";
2
+ import { promises as fs } from "node:fs";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import { runTool } from "../../toolchain/run.js";
6
+ import { executePlan, planFile, planMkdir } from "../../safety/index.js";
7
+ import { convert, getConverter } from "../converters/index.js";
8
+ import { defaultLocateTool } from "../toolchain.js";
9
+ import { checkTemplateInvalidation, computeTemplateCacheKey, templateCacheDir, templateContentFilename } from "./cache.js";
10
+ import { templateRecordSchema } from "./types.js";
11
+ async function safeReaddir(dir) {
12
+ try {
13
+ return (await fs.readdir(dir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
14
+ }
15
+ catch {
16
+ return [];
17
+ }
18
+ }
19
+ async function readRecordIfPresent(recordPath) {
20
+ let text;
21
+ try {
22
+ text = await fs.readFile(recordPath, "utf8");
23
+ }
24
+ catch {
25
+ return undefined;
26
+ }
27
+ let json;
28
+ try {
29
+ json = JSON.parse(text);
30
+ }
31
+ catch {
32
+ return undefined;
33
+ }
34
+ const parsed = templateRecordSchema.safeParse(json);
35
+ return parsed.success ? parsed.data : undefined;
36
+ }
37
+ function regexEscape(s) {
38
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
39
+ }
40
+ function extractionArgv(surface, container, internalPath, destDir) {
41
+ if (surface === "baldursgate3") {
42
+ return [
43
+ "-g", "bg3", "-a", "extract-package",
44
+ "-s", container, "-d", destDir,
45
+ "-x", `^${regexEscape(internalPath)}$`, "--use-regex",
46
+ ];
47
+ }
48
+ return ["extract", "-p", container, "-o", destDir, "-w", internalPath];
49
+ }
50
+ function contentExtFor(sourcePath, conversion) {
51
+ return conversion ? getConverter(conversion).to : path.extname(sourcePath);
52
+ }
53
+ export async function extractTemplate(input) {
54
+ const mode = input.mode ?? "dry-run";
55
+ const locateTool = input.locateTool ?? defaultLocateTool();
56
+ const toolId = input.surface === "baldursgate3" ? "divine" : "wolvenkit-cli";
57
+ const location = await locateTool(toolId);
58
+ const toolVersion = location.found ? (location.version ?? "unknown") : "unknown";
59
+ const key = computeTemplateCacheKey({
60
+ surface: input.surface,
61
+ gameVersion: input.gameVersion,
62
+ toolId,
63
+ toolVersion,
64
+ container: input.sourceContainer,
65
+ internalPath: input.sourcePath,
66
+ conversion: input.conversion,
67
+ });
68
+ const cacheDir = templateCacheDir(input.projectRoot, input.surface, input.gameVersion, key);
69
+ const recordPath = path.join(cacheDir, "record.json");
70
+ const contentFilename = templateContentFilename(input.sourcePath, contentExtFor(input.sourcePath, input.conversion));
71
+ const contentPath = path.join(cacheDir, contentFilename);
72
+ if (!input.refresh && mode === "apply") {
73
+ const existing = await readRecordIfPresent(recordPath);
74
+ if (existing) {
75
+ const invalidation = await checkTemplateInvalidation(existing, {
76
+ currentGameVersion: input.gameVersion,
77
+ locateTool,
78
+ contentAbsPath: contentPath,
79
+ });
80
+ if (invalidation.valid) {
81
+ return { action: "extract", mode, surface: input.surface, gameVersion: input.gameVersion, key, cacheDir, cached: true, invalidation, record: existing, applied: false, next: "Served from cache; no extraction ran." };
82
+ }
83
+ }
84
+ }
85
+ const extractionCommand = extractionArgv(input.surface, input.sourceContainer, input.sourcePath, "<cache>");
86
+ const conversionEntry = input.conversion ? getConverter(input.conversion) : undefined;
87
+ const planText = `${toolId} ${extractionCommand.join(" ")}${conversionEntry ? ` then ${conversionEntry.describe("<extracted>", "<content>")}` : ""}`;
88
+ if (mode === "dry-run") {
89
+ return {
90
+ action: "extract",
91
+ mode,
92
+ surface: input.surface,
93
+ gameVersion: input.gameVersion,
94
+ key,
95
+ cacheDir,
96
+ cached: false,
97
+ plan: planText,
98
+ applied: false,
99
+ next: 'Nothing was changed. Re-run with { mode: "apply" } to extract and cache this template.',
100
+ };
101
+ }
102
+ if (!location.found) {
103
+ return {
104
+ action: "extract",
105
+ mode,
106
+ surface: input.surface,
107
+ gameVersion: input.gameVersion,
108
+ key,
109
+ cacheDir,
110
+ cached: false,
111
+ applied: false,
112
+ next: location.hint,
113
+ };
114
+ }
115
+ const workDir = await fs.mkdtemp(path.join(os.tmpdir(), "modwright-text-extract-"));
116
+ try {
117
+ const rawDestDir = path.join(workDir, "raw");
118
+ await fs.mkdir(rawDestDir, { recursive: true });
119
+ const realArgv = extractionArgv(input.surface, input.sourceContainer, input.sourcePath, rawDestDir);
120
+ const rawOutputAbs = path.join(rawDestDir, input.sourcePath);
121
+ const result = await runTool(location, { args: realArgv, expectOutputs: [rawOutputAbs] });
122
+ if (!result.ok) {
123
+ return {
124
+ action: "extract",
125
+ mode,
126
+ surface: input.surface,
127
+ gameVersion: input.gameVersion,
128
+ key,
129
+ cacheDir,
130
+ cached: false,
131
+ applied: false,
132
+ next: `${toolId} extraction failed: ${result.error?.message ?? `exit ${result.exitCode}`}`,
133
+ };
134
+ }
135
+ let finalAbs = rawOutputAbs;
136
+ if (conversionEntry) {
137
+ const convertedAbs = path.join(workDir, `converted${conversionEntry.to.startsWith(".") ? conversionEntry.to : ".bin"}`);
138
+ const convResult = await convert(conversionEntry.id, rawOutputAbs, convertedAbs, { mode: "apply", locateTool, verifyRoundTrip: false });
139
+ if (!convResult.applied) {
140
+ return {
141
+ action: "extract",
142
+ mode,
143
+ surface: input.surface,
144
+ gameVersion: input.gameVersion,
145
+ key,
146
+ cacheDir,
147
+ cached: false,
148
+ applied: false,
149
+ next: `template conversion (${conversionEntry.id}) did not apply: ${convResult.next ?? "unknown reason"}`,
150
+ };
151
+ }
152
+ finalAbs = convertedAbs;
153
+ }
154
+ const content = await fs.readFile(finalAbs);
155
+ const sha256 = createHash("sha256").update(content).digest("hex");
156
+ const containerStat = await fs.stat(input.sourceContainer).catch(() => undefined);
157
+ const record = templateRecordSchema.parse({
158
+ ref: input.ref,
159
+ surface: input.surface,
160
+ kind: input.kind,
161
+ provenance: "vanilla-extract",
162
+ permission: input.permission,
163
+ sourcePath: input.sourcePath,
164
+ sourceContainer: input.sourceContainer,
165
+ containerSize: containerStat?.size,
166
+ containerMtime: containerStat?.mtime.toISOString(),
167
+ gameVersion: input.gameVersion,
168
+ toolId,
169
+ toolVersion,
170
+ extractionCommand,
171
+ conversion: input.conversion,
172
+ sha256,
173
+ sizeBytes: content.length,
174
+ extractedAt: new Date().toISOString(),
175
+ strippedPaths: [],
176
+ notes: input.notes,
177
+ });
178
+ const normalizedRecord = JSON.parse(JSON.stringify(record));
179
+ const plan = {
180
+ purpose: `extract template ${input.ref} (${input.surface}/${input.gameVersion})`,
181
+ operations: [planMkdir(cacheDir), planFile(contentPath, content), planFile(recordPath, `${JSON.stringify(normalizedRecord, null, 2)}\n`)],
182
+ backupRoot: path.join(input.projectRoot, ".modwright", "backups"),
183
+ };
184
+ const writeReport = await executePlan(plan, {
185
+ mode: "apply",
186
+ confidence: { level: "medium", assumptions: [`${toolId}'s extraction argv is unconfirmed against a real binary.`] },
187
+ });
188
+ return {
189
+ action: "extract",
190
+ mode,
191
+ surface: input.surface,
192
+ gameVersion: input.gameVersion,
193
+ key,
194
+ cacheDir,
195
+ cached: false,
196
+ record: normalizedRecord,
197
+ applied: writeReport.applied,
198
+ next: writeReport.next,
199
+ };
200
+ }
201
+ finally {
202
+ await fs.rm(workDir, { recursive: true, force: true });
203
+ }
204
+ }
205
+ export async function listTemplates(projectRoot, filter = {}) {
206
+ const cacheRoot = path.join(projectRoot, ".modwright", "cache", "templates");
207
+ const surfaces = filter.surface ? [filter.surface] : (await safeReaddir(cacheRoot));
208
+ const out = [];
209
+ for (const surface of surfaces) {
210
+ const surfaceDir = path.join(cacheRoot, surface);
211
+ const versions = filter.gameVersion ? [filter.gameVersion] : await safeReaddir(surfaceDir);
212
+ for (const gameVersion of versions) {
213
+ const versionDir = path.join(surfaceDir, gameVersion);
214
+ for (const key of await safeReaddir(versionDir)) {
215
+ const record = await readRecordIfPresent(path.join(versionDir, key, "record.json"));
216
+ if (record)
217
+ out.push({ key, surface, gameVersion, cacheDir: path.join(versionDir, key), record });
218
+ }
219
+ }
220
+ }
221
+ return out;
222
+ }
223
+ export async function getTemplate(projectRoot, surface, gameVersion, key, opts = {}) {
224
+ const cacheDir = templateCacheDir(projectRoot, surface, gameVersion, key);
225
+ const record = await readRecordIfPresent(path.join(cacheDir, "record.json"));
226
+ if (!record)
227
+ return undefined;
228
+ const contentPath = path.join(cacheDir, templateContentFilename(record.sourcePath, contentExtFor(record.sourcePath, record.conversion)));
229
+ const invalidation = opts.currentGameVersion !== undefined || opts.locateTool
230
+ ? await checkTemplateInvalidation(record, { currentGameVersion: opts.currentGameVersion, locateTool: opts.locateTool, contentAbsPath: contentPath })
231
+ : undefined;
232
+ return { record, cacheDir, contentPath, invalidation };
233
+ }
234
+ export async function invalidateTemplate(projectRoot, surface, gameVersion, key, mode = "dry-run") {
235
+ const cacheDir = templateCacheDir(projectRoot, surface, gameVersion, key);
236
+ const plan = {
237
+ purpose: `invalidate cached template ${surface}/${gameVersion}/${key}`,
238
+ operations: [{ kind: "delete", path: cacheDir }],
239
+ backupRoot: path.join(projectRoot, ".modwright", "backups"),
240
+ };
241
+ return executePlan(plan, {
242
+ mode,
243
+ confidence: { level: "high", assumptions: ["Only the untracked template cache (.modwright/cache/templates/) is touched; the tracked authoring manifest is never affected."] },
244
+ });
245
+ }
@@ -0,0 +1,51 @@
1
+ import { z } from "zod";
2
+ export const TEMPLATE_PROVENANCE = ["vanilla-extract", "own-work", "authored"];
3
+ export const TEMPLATE_PERMISSION = ["never-redistribute", "maintainer-own", "declared-dependency"];
4
+ const SHA256_PATTERN = /^[0-9a-f]{64}$/;
5
+ export const templateRecordSchema = z
6
+ .object({
7
+ ref: z.string(),
8
+ surface: z.enum(["baldursgate3", "cyberpunk2077"]),
9
+ kind: z.string(),
10
+ provenance: z.enum(TEMPLATE_PROVENANCE),
11
+ permission: z.enum(TEMPLATE_PERMISSION),
12
+ sourcePath: z.string(),
13
+ sourceContainer: z.string().optional(),
14
+ containerSize: z.number().int().optional(),
15
+ containerMtime: z.string().optional(),
16
+ gameVersion: z.string(),
17
+ toolId: z.string(),
18
+ toolVersion: z.string(),
19
+ extractionCommand: z.array(z.string()),
20
+ conversion: z.string().optional(),
21
+ sha256: z.string().regex(SHA256_PATTERN),
22
+ sizeBytes: z.number().int(),
23
+ extractedAt: z.string(),
24
+ strippedPaths: z.array(z.string()).default([]),
25
+ notes: z.string().optional(),
26
+ })
27
+ .strict();
28
+ export function templateCacheDir(projectRoot, surface, gameVersion, key) {
29
+ return [projectRoot, ".modwright", "cache", "templates", surface, gameVersion, key].join("/");
30
+ }
31
+ export const TEMPLATE_MANIFEST_FILE = ".modwright/templates/manifest.json";
32
+ export const AUTHORING_MANIFEST_SCHEMA_VERSION = 1;
33
+ export const authoringManifestEntrySchema = z
34
+ .object({
35
+ path: z.string(),
36
+ derivedFrom: z.string(),
37
+ provenance: z.enum(TEMPLATE_PROVENANCE),
38
+ permission: z.enum(TEMPLATE_PERMISSION),
39
+ templateSha256: z.string().regex(SHA256_PATTERN),
40
+ authoredSha256: z.string().regex(SHA256_PATTERN),
41
+ rewrittenPaths: z.array(z.string()),
42
+ scaffold: z.string(),
43
+ writtenAt: z.string(),
44
+ })
45
+ .strict();
46
+ export const authoringManifestSchema = z
47
+ .object({
48
+ schemaVersion: z.literal(AUTHORING_MANIFEST_SCHEMA_VERSION),
49
+ entries: z.array(authoringManifestEntrySchema),
50
+ })
51
+ .strict();
@@ -0,0 +1,16 @@
1
+ import { locateTool as coreLocateTool, TOOL_SPECS } from "../toolchain/index.js";
2
+ export function defaultLocateTool(projectOverrides, probe = "all") {
3
+ return async (id) => {
4
+ const spec = TOOL_SPECS.find((s) => s.id === id);
5
+ if (!spec) {
6
+ return {
7
+ id,
8
+ label: id,
9
+ found: false,
10
+ usedBy: [],
11
+ hint: `"${id}" is not a known tool id in ModWright's toolchain catalog (see core/toolchain/specs.ts).`,
12
+ };
13
+ }
14
+ return coreLocateTool(spec, { projectOverrides, probe });
15
+ };
16
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,149 @@
1
+ import { promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ import { walk } from "../fsutil.js";
4
+ import { sha256File } from "../build/manifest.js";
5
+ import { listTemplates } from "./templates/library.js";
6
+ import { TEMPLATE_MANIFEST_FILE, authoringManifestSchema } from "./templates/types.js";
7
+ const GROUPS = ["history"];
8
+ const MOD_TYPES = ["packaging"];
9
+ function isProjectTarget(target) {
10
+ return target.kind === "project";
11
+ }
12
+ function toPosix(p) {
13
+ return p.split(path.sep).join("/");
14
+ }
15
+ function projectRelative(root, abs) {
16
+ return toPosix(path.relative(root, abs));
17
+ }
18
+ async function readAuthoringManifest(root) {
19
+ const manifestPath = path.join(root, TEMPLATE_MANIFEST_FILE);
20
+ let text;
21
+ try {
22
+ text = await fs.readFile(manifestPath, "utf8");
23
+ }
24
+ catch {
25
+ return new Map();
26
+ }
27
+ let json;
28
+ try {
29
+ json = JSON.parse(text);
30
+ }
31
+ catch {
32
+ return new Map();
33
+ }
34
+ const parsed = authoringManifestSchema.safeParse(json);
35
+ if (!parsed.success)
36
+ return new Map();
37
+ return new Map(parsed.data.entries.map((e) => [toPosix(e.path), e]));
38
+ }
39
+ async function vanillaExtractShaIndex(root, surface) {
40
+ const entries = await listTemplates(root, { surface });
41
+ const index = new Map();
42
+ for (const e of entries) {
43
+ if (e.record.provenance === "vanilla-extract")
44
+ index.set(e.record.sha256, e.record.ref);
45
+ }
46
+ return index;
47
+ }
48
+ function collectRoots(project) {
49
+ const roots = [];
50
+ if (project.build?.absStaging)
51
+ roots.push({ label: "build.staging", abs: project.build.absStaging });
52
+ if (project.build?.outDirExists && project.build.absOutDir)
53
+ roots.push({ label: "build.outDir", abs: project.build.absOutDir });
54
+ const targets = project.project.deploy?.targets ?? [];
55
+ for (const target of targets) {
56
+ const sourceMatch = project.sources.find((s) => s.id === target.from && s.exists);
57
+ if (sourceMatch) {
58
+ roots.push({ label: `deploy target "${target.from}" (source)`, abs: sourceMatch.absPath });
59
+ continue;
60
+ }
61
+ if (project.build?.absOutDir) {
62
+ roots.push({ label: `deploy target "${target.from}"`, abs: path.join(project.build.absOutDir, target.from) });
63
+ }
64
+ }
65
+ return roots;
66
+ }
67
+ export function vanillaExtractNeverShipValidator(opts) {
68
+ const surface = opts.game;
69
+ return {
70
+ id: opts.id,
71
+ game: opts.game,
72
+ groups: GROUPS,
73
+ modTypes: MOD_TYPES,
74
+ summary: "Blocks a build whose staged/dist/deploy output carries a verbatim vanilla-extract template (by sha256), " +
75
+ "or one the tracked authoring manifest records as derived from such a template with no rewrittenPaths — a " +
76
+ "curated shipped copy of vanilla content is a licence violation, and non-negotiable.",
77
+ appliesTo: isProjectTarget,
78
+ appliesToDescription: "the whole project: build.staging, build.outDir (once it exists on disk) and every resolved deploy.targets[].from root",
79
+ needs: { project: true },
80
+ cost: "build",
81
+ severity: "block",
82
+ evidence: opts.evidence,
83
+ async run(ctx) {
84
+ const project = ctx.project;
85
+ const findings = [];
86
+ const manifestByPath = await readAuthoringManifest(project.root);
87
+ const shaIndex = await vanillaExtractShaIndex(project.root, surface);
88
+ if (manifestByPath.size === 0 && shaIndex.size === 0)
89
+ return findings;
90
+ const seen = new Set();
91
+ for (const root of collectRoots(project)) {
92
+ const { files, truncated } = await walk(root.abs, { limit: 50_000 });
93
+ for (const rel of files) {
94
+ const abs = path.join(root.abs, rel);
95
+ if (seen.has(abs))
96
+ continue;
97
+ seen.add(abs);
98
+ const relPosix = toPosix(rel);
99
+ const projectRel = projectRelative(project.root, abs);
100
+ const manifestEntry = manifestByPath.get(projectRel);
101
+ if (manifestEntry && manifestEntry.provenance === "vanilla-extract" && manifestEntry.rewrittenPaths.length === 0) {
102
+ findings.push({
103
+ validatorId: opts.id,
104
+ severity: "block",
105
+ file: abs,
106
+ message: `"${relPosix}" under ${root.label} (${root.abs}) is recorded in ${TEMPLATE_MANIFEST_FILE} as derived from the ` +
107
+ `vanilla-extract template "${manifestEntry.derivedFrom}" (written by scaffold "${manifestEntry.scaffold}") with ` +
108
+ `an empty rewrittenPaths — a copy with the serial numbers not even filed off.`,
109
+ fix: `Rewrite at least one field before this file ships, or drop it from the deploy/dist set.`,
110
+ evidence: opts.evidence,
111
+ related: [path.join(project.root, TEMPLATE_MANIFEST_FILE)],
112
+ });
113
+ continue;
114
+ }
115
+ let sha256;
116
+ try {
117
+ sha256 = await sha256File(abs);
118
+ }
119
+ catch {
120
+ continue;
121
+ }
122
+ const ref = shaIndex.get(sha256);
123
+ if (ref) {
124
+ findings.push({
125
+ validatorId: opts.id,
126
+ severity: "block",
127
+ file: abs,
128
+ message: `"${relPosix}" under ${root.label} (${root.abs}) is byte-identical (sha256 ${sha256}) to the cached ` +
129
+ `vanilla-extract template "${ref}" — a verbatim copy of vanilla content. Vanilla game data is never ` +
130
+ `redistributed and never shipped, by ModWright or by a mod built with it.`,
131
+ fix: `Remove this file from the shipping set, or run it through a scaffold that rewrites at least one field.`,
132
+ evidence: opts.evidence,
133
+ });
134
+ }
135
+ }
136
+ if (truncated) {
137
+ findings.push({
138
+ validatorId: opts.id,
139
+ severity: "info",
140
+ file: root.abs,
141
+ message: `${root.label} has more files than this scan's limit; the vanilla-extract-never-ships scan may be incomplete for it.`,
142
+ evidence: opts.evidence,
143
+ });
144
+ }
145
+ }
146
+ return findings;
147
+ },
148
+ };
149
+ }