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,261 @@
1
+ import { promises as fs } from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import { assertAbsolute, runTool } from "../../toolchain/run.js";
5
+ import { planFile, executePlan } from "../../safety/index.js";
6
+ import { fieldDiff } from "../diff/index.js";
7
+ import { renumberHandles } from "../handles.js";
8
+ import { parseJson } from "../json.js";
9
+ import { defaultLocateTool } from "../toolchain.js";
10
+ import { BG3_CONVERTERS } from "./bg3.js";
11
+ import { CYBERPUNK_CONVERTERS } from "./cyberpunk.js";
12
+ export { BG3_CONVERTERS } from "./bg3.js";
13
+ export { CYBERPUNK_CONVERTERS } from "./cyberpunk.js";
14
+ export const CONVERTERS = [...BG3_CONVERTERS, ...CYBERPUNK_CONVERTERS];
15
+ const BY_ID = new Map(CONVERTERS.map((c) => [c.id, c]));
16
+ export function getConverter(id) {
17
+ const entry = BY_ID.get(id);
18
+ if (!entry) {
19
+ throw new Error(`text convert: unknown conversion id "${id}". Known: ${CONVERTERS.map((c) => c.id).join(", ")}`);
20
+ }
21
+ return entry;
22
+ }
23
+ function scratchExt(kindLabel) {
24
+ return kindLabel.startsWith(".") ? kindLabel : ".bin";
25
+ }
26
+ async function pathExists(p) {
27
+ try {
28
+ await fs.access(p);
29
+ return true;
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ async function soleFileIn(dir, expectedName) {
36
+ const entries = await fs.readdir(dir, { withFileTypes: true });
37
+ const files = entries.filter((e) => e.isFile()).map((e) => e.name);
38
+ if (files.length === 1)
39
+ return path.join(dir, files[0]);
40
+ if (files.length === 0) {
41
+ throw new Error(`no output file found in ${dir} (expected "${expectedName}")`);
42
+ }
43
+ throw new Error(`ambiguous output in ${dir}: expected "${expectedName}" but found ${files.length} files (${files.join(", ")})`);
44
+ }
45
+ function destinationArg(entry, destAbs) {
46
+ return entry.destinationKind === "output-directory" ? path.dirname(destAbs) : destAbs;
47
+ }
48
+ async function runConverterTool(entry, sourceAbs, targetAbs, location, tmpRoot) {
49
+ if (entry.destinationKind === "output-directory") {
50
+ await fs.mkdir(tmpRoot, { recursive: true });
51
+ const outDir = await fs.mkdtemp(path.join(tmpRoot, "modwright-convert-"));
52
+ const cleanup = async () => {
53
+ await fs.rm(outDir, { recursive: true, force: true }).catch(() => { });
54
+ };
55
+ const argv = entry.argv(sourceAbs, outDir);
56
+ const result = await runTool(location, { args: argv });
57
+ if (!result.ok) {
58
+ return { argv, ok: false, errorMessage: result.error?.message ?? `exit ${result.exitCode}`, cleanup };
59
+ }
60
+ const expectedName = entry.expectedOutputName ? entry.expectedOutputName(sourceAbs) : path.basename(targetAbs);
61
+ const expectedPath = path.join(outDir, expectedName);
62
+ try {
63
+ const producedAbs = (await pathExists(expectedPath)) ? expectedPath : await soleFileIn(outDir, expectedName);
64
+ return { argv, ok: true, producedAbs, cleanup };
65
+ }
66
+ catch (err) {
67
+ return { argv, ok: false, errorMessage: err.message, cleanup };
68
+ }
69
+ }
70
+ const argv = entry.argv(sourceAbs, targetAbs);
71
+ const result = await runTool(location, { args: argv, expectOutputs: [targetAbs] });
72
+ if (!result.ok) {
73
+ return { argv, ok: false, errorMessage: result.error?.message ?? `exit ${result.exitCode}` };
74
+ }
75
+ return { argv, ok: true, producedAbs: targetAbs };
76
+ }
77
+ async function runRoundTrip(entry, originalSourceAbs, producedAbs, entryLocation, locateTool, workDir) {
78
+ const inverse = getConverter(entry.inverse);
79
+ const inverseLocation = await locateTool(inverse.toolId);
80
+ if (!inverseLocation.found) {
81
+ return {
82
+ ran: false,
83
+ skippedReason: `round-trip verification needs ${inverse.toolId}, which was not found: ${inverseLocation.hint ?? "not located"}`,
84
+ };
85
+ }
86
+ const backTarget = path.join(workDir, `roundtrip-back${scratchExt(inverse.to)}`);
87
+ const inverseOutcome = await runConverterTool(inverse, producedAbs, backTarget, inverseLocation, workDir);
88
+ if (!inverseOutcome.ok) {
89
+ await inverseOutcome.cleanup?.();
90
+ return { ran: true, ok: false, skippedReason: `round-trip conversion (${inverse.id}) failed: ${inverseOutcome.errorMessage}` };
91
+ }
92
+ const backAbs = inverseOutcome.producedAbs;
93
+ if (entry.roundTrip === "forward-and-byte-compare") {
94
+ const backBytes = await fs.readFile(backAbs);
95
+ const originalBytes = await fs.readFile(originalSourceAbs);
96
+ const bytesMatch = backBytes.equals(originalBytes);
97
+ if (bytesMatch) {
98
+ await inverseOutcome.cleanup?.();
99
+ return { ran: true, ok: true, bytesMatch, note: "byte compare: round-tripped binary is byte-identical to the original." };
100
+ }
101
+ const diffKind = inverse.diffKind;
102
+ if (!diffKind) {
103
+ await inverseOutcome.cleanup?.();
104
+ return {
105
+ ran: true,
106
+ ok: false,
107
+ bytesMatch,
108
+ note: "byte compare failed and no field-diff kind is registered for this format, so semantic equivalence could not be checked.",
109
+ };
110
+ }
111
+ const secondTextTarget = path.join(workDir, `roundtrip-second${scratchExt(entry.to)}`);
112
+ const secondOutcome = await runConverterTool(entry, backAbs, secondTextTarget, entryLocation, workDir);
113
+ if (!secondOutcome.ok) {
114
+ await inverseOutcome.cleanup?.();
115
+ await secondOutcome.cleanup?.();
116
+ return {
117
+ ran: true,
118
+ ok: false,
119
+ bytesMatch,
120
+ note: `byte compare failed; the semantic check's own re-conversion (${entry.id}) failed too, so semantic equivalence could not be checked: ${secondOutcome.errorMessage}`,
121
+ };
122
+ }
123
+ const firstText = await fs.readFile(producedAbs, "utf8");
124
+ const secondText = await fs.readFile(secondOutcome.producedAbs, "utf8");
125
+ await inverseOutcome.cleanup?.();
126
+ await secondOutcome.cleanup?.();
127
+ const diff = fieldDiff(diffKind, firstText, secondText);
128
+ return {
129
+ ran: true,
130
+ ok: diff.clean,
131
+ bytesMatch,
132
+ semanticMatch: diff.clean,
133
+ diff: diff.rows,
134
+ note: diff.clean
135
+ ? "byte compare failed (non-deterministic binary serialization) but a further conversion field-diffed clean against the first output; treated as a pass."
136
+ : "byte compare failed and the semantic field diff also found real differences.",
137
+ };
138
+ }
139
+ const originalText = await fs.readFile(originalSourceAbs, "utf8");
140
+ const roundTrippedText = await fs.readFile(backAbs, "utf8");
141
+ const diff = fieldDiff(entry.diffKind, originalText, roundTrippedText);
142
+ await inverseOutcome.cleanup?.();
143
+ return { ran: true, ok: diff.clean, diff: diff.rows };
144
+ }
145
+ export async function convert(id, sourceAbs, destAbs, options = {}) {
146
+ const entry = getConverter(id);
147
+ assertAbsolute([sourceAbs, destAbs]);
148
+ const mode = options.mode ?? "dry-run";
149
+ const locateTool = options.locateTool ?? defaultLocateTool();
150
+ const location = await locateTool(entry.toolId);
151
+ const renumberNote = entry.id === "cp2077.json-to-cr2w" ? " (HandleId/BufferId renumbered first)" : "";
152
+ const destNote = entry.destinationKind === "output-directory"
153
+ ? ` (-o is an output DIRECTORY the tool names its own file in; an apply runs it in a scratch directory and writes the result to ${destAbs})`
154
+ : "";
155
+ if (mode === "dry-run") {
156
+ return {
157
+ action: "convert",
158
+ conversionId: entry.id,
159
+ mode,
160
+ source: sourceAbs,
161
+ dest: destAbs,
162
+ tool: entry.toolId,
163
+ toolVersion: location.version,
164
+ argv: entry.argv(sourceAbs, destinationArg(entry, destAbs)),
165
+ plan: `${entry.describe(sourceAbs, destinationArg(entry, destAbs))}${renumberNote}${destNote}`,
166
+ applied: false,
167
+ toolLocation: location,
168
+ next: 'Nothing was changed. Re-run with { mode: "apply" } to run the tool and write the result; an existing destination is backed up first.',
169
+ };
170
+ }
171
+ if (!location.found) {
172
+ return {
173
+ action: "convert",
174
+ conversionId: entry.id,
175
+ mode,
176
+ source: sourceAbs,
177
+ dest: destAbs,
178
+ tool: entry.toolId,
179
+ argv: entry.argv(sourceAbs, destinationArg(entry, destAbs)),
180
+ applied: false,
181
+ toolLocation: location,
182
+ next: location.hint,
183
+ };
184
+ }
185
+ const workDir = await fs.mkdtemp(path.join(os.tmpdir(), "modwright-text-convert-"));
186
+ try {
187
+ let actualSourceAbs = sourceAbs;
188
+ let handleRenumbering;
189
+ if (entry.id === "cp2077.json-to-cr2w") {
190
+ const text = await fs.readFile(sourceAbs, "utf8");
191
+ const parsed = parseJson(text, sourceAbs);
192
+ const { root, renumbered } = renumberHandles(parsed);
193
+ handleRenumbering = renumbered.map(({ key, from, to }) => ({ key, from, to }));
194
+ const renumberDir = path.join(workDir, "renumbered");
195
+ await fs.mkdir(renumberDir, { recursive: true });
196
+ actualSourceAbs = path.join(renumberDir, `${path.basename(destAbs)}.json`);
197
+ await fs.writeFile(actualSourceAbs, JSON.stringify(root));
198
+ }
199
+ const scratchDest = path.join(workDir, `produced${scratchExt(entry.to)}`);
200
+ const runOutcome = await runConverterTool(entry, actualSourceAbs, scratchDest, location, workDir);
201
+ const argvUsed = runOutcome.argv;
202
+ if (!runOutcome.ok) {
203
+ await runOutcome.cleanup?.();
204
+ return {
205
+ action: "convert",
206
+ conversionId: entry.id,
207
+ mode,
208
+ source: sourceAbs,
209
+ dest: destAbs,
210
+ tool: entry.toolId,
211
+ toolVersion: location.version,
212
+ argv: argvUsed,
213
+ applied: false,
214
+ handleRenumbering,
215
+ toolLocation: location,
216
+ next: `${entry.toolId} failed: ${runOutcome.errorMessage}`,
217
+ };
218
+ }
219
+ const producedAbs = runOutcome.producedAbs;
220
+ const content = await fs.readFile(producedAbs);
221
+ const plan = {
222
+ purpose: `text convert ${entry.id}: ${path.basename(sourceAbs)} -> ${path.basename(destAbs)}`,
223
+ operations: [planFile(destAbs, content)],
224
+ backupRoot: path.join(path.dirname(destAbs), ".modwright", "backups"),
225
+ };
226
+ const writeReport = await executePlan(plan, {
227
+ mode: "apply",
228
+ confidence: {
229
+ level: "medium",
230
+ assumptions: [
231
+ `${entry.toolId}'s argv shape for this conversion follows ${entry.describe("<source>", "<dest>")}; ` +
232
+ "both CR2W directions' argv were checked against the real WolvenKit CLI, " +
233
+ "but the produced file's own name is only confirmed for `convert serialize`.",
234
+ ],
235
+ },
236
+ });
237
+ let roundTrip;
238
+ if (options.verifyRoundTrip !== false) {
239
+ roundTrip = await runRoundTrip(entry, sourceAbs, producedAbs, location, locateTool, workDir);
240
+ }
241
+ await runOutcome.cleanup?.();
242
+ return {
243
+ action: "convert",
244
+ conversionId: entry.id,
245
+ mode,
246
+ source: sourceAbs,
247
+ dest: destAbs,
248
+ tool: entry.toolId,
249
+ toolVersion: location.version,
250
+ argv: argvUsed,
251
+ applied: writeReport.applied,
252
+ handleRenumbering,
253
+ roundTrip,
254
+ toolLocation: location,
255
+ next: writeReport.next,
256
+ };
257
+ }
258
+ finally {
259
+ await fs.rm(workDir, { recursive: true, force: true });
260
+ }
261
+ }
@@ -0,0 +1,180 @@
1
+ import { dollarType, dollarValue, isJsonArray, isJsonObject } from "../json.js";
2
+ const DOCUMENT_LEVEL_FIELDS = ["Data.Version", "Data.BuildVersion", "Data.RootChunk.cookingPlatform", "cookingPlatform"];
3
+ const IGNORED_EXACT_PATHS = new Set([
4
+ "$.Header.ExportedDateTime",
5
+ "$.Header.ArchiveFileName",
6
+ ...DOCUMENT_LEVEL_FIELDS.map((f) => `$.${f}`),
7
+ ]);
8
+ const IGNORED_KEY_SEGMENTS = new Set(["renderResourceBlob", "parameters"]);
9
+ const IGNORED_KEY_NAMES = new Set(["HandleId", "BufferId"]);
10
+ function lastSegmentIsIgnored(path) {
11
+ for (const seg of IGNORED_KEY_SEGMENTS) {
12
+ if (path === `$.${seg}` || path.endsWith(`.${seg}`) || path.includes(`.${seg}.`) || path.includes(`.${seg}[`)) {
13
+ return true;
14
+ }
15
+ }
16
+ return false;
17
+ }
18
+ function isIgnoredPath(path) {
19
+ return IGNORED_EXACT_PATHS.has(path) || lastSegmentIsIgnored(path);
20
+ }
21
+ function normalizeResourceLike(s) {
22
+ return s.replace(/\//g, "\\").replace(/^\\+/, "").toLowerCase();
23
+ }
24
+ const RELATIVE_EPSILON = 1e-6;
25
+ function scalarsEqual(type, left, right) {
26
+ if (left === right)
27
+ return true;
28
+ if (left === undefined || right === undefined)
29
+ return false;
30
+ if (typeof left === "number" && typeof right === "number") {
31
+ if (type === "Float" || type === "Double" || (!Number.isInteger(left) && !Number.isInteger(right))) {
32
+ const denom = Math.max(Math.abs(left), Math.abs(right), 1);
33
+ return Math.abs(left - right) / denom <= RELATIVE_EPSILON;
34
+ }
35
+ return left === right;
36
+ }
37
+ if (typeof left === "string" && typeof right === "string") {
38
+ if (type === "CName" || type === "ResourcePath") {
39
+ return normalizeResourceLike(left) === normalizeResourceLike(right);
40
+ }
41
+ return left === right;
42
+ }
43
+ return false;
44
+ }
45
+ function handleElementIdentity(el) {
46
+ if (!isJsonObject(el) || !("Data" in el))
47
+ return undefined;
48
+ const data = el.Data;
49
+ if (!isJsonObject(data))
50
+ return `handle:${dollarType(data) ?? ""}`;
51
+ const type = dollarType(data) ?? (typeof data.$type === "string" ? data.$type : "");
52
+ const nameField = data.name ?? data.appearanceName ?? data.debugName;
53
+ const name = typeof nameField === "string" ? nameField : dollarValue(nameField);
54
+ return `handle:${type}:${typeof name === "string" ? name : ""}`;
55
+ }
56
+ function pairArrayElements(left, right) {
57
+ const leftKeyed = left.map((value, index) => ({ key: handleElementIdentity(value) ?? `#${index}`, index, value }));
58
+ const rightKeyed = right.map((value, index) => ({ key: handleElementIdentity(value) ?? `#${index}`, index, value }));
59
+ const usePosition = leftKeyed.some((k) => k.key.startsWith("#")) || rightKeyed.some((k) => k.key.startsWith("#"));
60
+ if (usePosition) {
61
+ const len = Math.max(left.length, right.length);
62
+ const out = [];
63
+ for (let i = 0; i < len; i++) {
64
+ out.push({ path: `[${i}]`, l: leftKeyed[i], r: rightKeyed[i] });
65
+ }
66
+ return out;
67
+ }
68
+ const rightByKey = new Map();
69
+ for (const k of rightKeyed) {
70
+ const list = rightByKey.get(k.key) ?? [];
71
+ list.push(k);
72
+ rightByKey.set(k.key, list);
73
+ }
74
+ const consumed = new Set();
75
+ const out = [];
76
+ for (const l of leftKeyed) {
77
+ const candidates = rightByKey.get(l.key) ?? [];
78
+ const match = candidates.find((c) => !consumed.has(c));
79
+ if (match) {
80
+ consumed.add(match);
81
+ out.push({ path: `[key:${l.key}]`, l, r: match });
82
+ }
83
+ else {
84
+ out.push({ path: `[key:${l.key}]`, l, r: undefined });
85
+ }
86
+ }
87
+ for (const r of rightKeyed) {
88
+ if (!consumed.has(r))
89
+ out.push({ path: `[key:${r.key}]`, l: undefined, r });
90
+ }
91
+ return out;
92
+ }
93
+ function pushDocumentLevelFieldDiffs(left, right, rows) {
94
+ for (const field of DOCUMENT_LEVEL_FIELDS) {
95
+ const l = getByDotPath(left, field);
96
+ const r = getByDotPath(right, field);
97
+ if (l === undefined && r === undefined)
98
+ continue;
99
+ if (JSON.stringify(l) !== JSON.stringify(r)) {
100
+ rows.push({
101
+ path: `$.${field}`,
102
+ kind: "changed",
103
+ left: l,
104
+ right: r,
105
+ note: "tool-version field, reported once at the document level rather than per node",
106
+ });
107
+ return;
108
+ }
109
+ }
110
+ }
111
+ function getByDotPath(root, dotPath) {
112
+ let cur = root;
113
+ for (const seg of dotPath.split(".")) {
114
+ if (!isJsonObject(cur))
115
+ return undefined;
116
+ cur = cur[seg];
117
+ }
118
+ return cur;
119
+ }
120
+ function compareNode(left, right, path, rows) {
121
+ if (isIgnoredPath(path))
122
+ return;
123
+ const parentKey = path.split(/[.[]/).pop()?.replace(/\]$/, "");
124
+ if (parentKey && IGNORED_KEY_NAMES.has(parentKey))
125
+ return;
126
+ if (left === undefined && right === undefined)
127
+ return;
128
+ if (left === undefined) {
129
+ rows.push({ path, kind: "added", right });
130
+ return;
131
+ }
132
+ if (right === undefined) {
133
+ rows.push({ path, kind: "removed", left });
134
+ return;
135
+ }
136
+ const leftIsTypedScalar = isJsonObject(left) && "$value" in left;
137
+ const rightIsTypedScalar = isJsonObject(right) && "$value" in right;
138
+ if (leftIsTypedScalar || rightIsTypedScalar) {
139
+ const type = dollarType(left) ?? dollarType(right);
140
+ const lv = dollarValue(left);
141
+ const rv = dollarValue(right);
142
+ if (!scalarsEqual(type, lv, rv)) {
143
+ rows.push({ path, kind: "changed", left: lv, right: rv });
144
+ }
145
+ return;
146
+ }
147
+ if (isJsonArray(left) && isJsonArray(right)) {
148
+ for (const { path: sub, l, r } of pairArrayElements(left, right)) {
149
+ compareNode(l?.value, r?.value, `${path}${sub}`, rows);
150
+ }
151
+ return;
152
+ }
153
+ if (isJsonArray(left) !== isJsonArray(right)) {
154
+ rows.push({ path, kind: "changed", left, right });
155
+ return;
156
+ }
157
+ if (isJsonObject(left) && isJsonObject(right)) {
158
+ const keys = new Set([...Object.keys(left), ...Object.keys(right)]);
159
+ for (const key of keys) {
160
+ if (IGNORED_KEY_NAMES.has(key))
161
+ continue;
162
+ compareNode(left[key], right[key], `${path}.${key}`, rows);
163
+ }
164
+ return;
165
+ }
166
+ if (!scalarsEqual(undefined, left, right)) {
167
+ rows.push({ path, kind: "changed", left, right });
168
+ }
169
+ }
170
+ export function diffCr2wJson(left, right) {
171
+ const rows = [];
172
+ pushDocumentLevelFieldDiffs(left, right, rows);
173
+ compareNode(left, right, "$", rows);
174
+ return { rows, clean: rows.length === 0 };
175
+ }
176
+ export function diffCr2wJsonText(leftText, rightText) {
177
+ const left = JSON.parse(leftText);
178
+ const right = JSON.parse(rightText);
179
+ return diffCr2wJson(left, right);
180
+ }
@@ -0,0 +1,16 @@
1
+ import { diffCr2wJsonText } from "./cr2w.js";
2
+ import { diffLocaXml } from "./loca.js";
3
+ import { diffLsx } from "./lsx.js";
4
+ export { diffCr2wJson, diffCr2wJsonText } from "./cr2w.js";
5
+ export { diffLocaXml, diffLocaEntries, parseLocaXml } from "./loca.js";
6
+ export { diffLsx } from "./lsx.js";
7
+ export function fieldDiff(kind, leftText, rightText) {
8
+ switch (kind) {
9
+ case "cr2w-json":
10
+ return diffCr2wJsonText(leftText, rightText);
11
+ case "lsx":
12
+ return diffLsx(leftText, rightText);
13
+ case "loca-xml":
14
+ return diffLocaXml(leftText, rightText);
15
+ }
16
+ }
@@ -0,0 +1,43 @@
1
+ import { findAll, parseXml } from "../xml.js";
2
+ export function parseLocaXml(xml) {
3
+ const root = parseXml(xml);
4
+ return findAll(root, "content").map((el) => ({
5
+ contentuid: el.attrs.contentuid ?? "",
6
+ version: el.attrs.version ?? "1",
7
+ text: el.text,
8
+ }));
9
+ }
10
+ function key(e) {
11
+ return e.contentuid;
12
+ }
13
+ export function diffLocaEntries(left, right) {
14
+ const rows = [];
15
+ const rightByKey = new Map(right.map((e) => [key(e), e]));
16
+ const seen = new Set();
17
+ for (const l of left) {
18
+ const k = key(l);
19
+ seen.add(k);
20
+ const r = rightByKey.get(k);
21
+ if (!r) {
22
+ rows.push({ path: `$.content[contentuid=${k}]`, kind: "removed", left: l });
23
+ continue;
24
+ }
25
+ if (l.version !== r.version || l.text !== r.text) {
26
+ rows.push({
27
+ path: `$.content[contentuid=${k}]`,
28
+ kind: "changed",
29
+ left: { version: l.version, text: l.text },
30
+ right: { version: r.version, text: r.text },
31
+ });
32
+ }
33
+ }
34
+ for (const r of right) {
35
+ const k = key(r);
36
+ if (!seen.has(k))
37
+ rows.push({ path: `$.content[contentuid=${k}]`, kind: "added", right: r });
38
+ }
39
+ return { rows, clean: rows.length === 0 };
40
+ }
41
+ export function diffLocaXml(leftXml, rightXml) {
42
+ return diffLocaEntries(parseLocaXml(leftXml), parseLocaXml(rightXml));
43
+ }
@@ -0,0 +1,126 @@
1
+ import { parseXml } from "../xml.js";
2
+ const IGNORED_ATTRIBUTE_IDS = new Set(["FileSize", "MD5"]);
3
+ function attributeChildren(el) {
4
+ return el.children.filter((c) => c.tag === "attribute");
5
+ }
6
+ function structuralChildren(el, tag) {
7
+ const out = [];
8
+ for (const c of el.children) {
9
+ if (c.tag === tag)
10
+ out.push(c);
11
+ else if (c.tag === "children")
12
+ out.push(...structuralChildren(c, tag));
13
+ }
14
+ return out;
15
+ }
16
+ function nodeIdentity(el) {
17
+ for (const key of ["UUID", "MapKey", "Name"]) {
18
+ const attr = attributeChildren(el).find((a) => a.attrs.id === key);
19
+ if (attr)
20
+ return `${key}=${attr.attrs.value ?? ""}`;
21
+ }
22
+ return undefined;
23
+ }
24
+ function segmentFor(tag, el) {
25
+ const id = el.attrs.id;
26
+ return id !== undefined ? `${tag}#${id}` : undefined;
27
+ }
28
+ function pairSiblings(tag, left, right) {
29
+ const groupKey = (el) => segmentFor(tag, el) ?? "#no-id";
30
+ const leftGroups = new Map();
31
+ const rightGroups = new Map();
32
+ for (const el of left) {
33
+ const k = groupKey(el);
34
+ (leftGroups.get(k) ?? leftGroups.set(k, []).get(k)).push(el);
35
+ }
36
+ for (const el of right) {
37
+ const k = groupKey(el);
38
+ (rightGroups.get(k) ?? rightGroups.set(k, []).get(k)).push(el);
39
+ }
40
+ const pairs = [];
41
+ const allKeys = new Set([...leftGroups.keys(), ...rightGroups.keys()]);
42
+ for (const k of allKeys) {
43
+ const ls = leftGroups.get(k) ?? [];
44
+ const rs = rightGroups.get(k) ?? [];
45
+ const segment = k === "#no-id" ? undefined : k;
46
+ const multi = ls.length > 1 || rs.length > 1;
47
+ const anyIdentity = multi && [...ls, ...rs].some((el) => nodeIdentity(el) !== undefined);
48
+ if (anyIdentity) {
49
+ const rsByIdentity = new Map(rs.map((el) => [nodeIdentity(el) ?? "#pos", el]));
50
+ const usedR = new Set();
51
+ for (const l of ls) {
52
+ const idn = nodeIdentity(l) ?? "#pos";
53
+ const r = rsByIdentity.get(idn);
54
+ if (r)
55
+ usedR.add(r);
56
+ pairs.push({ l, r, segment: segment && idn !== "#pos" ? `${segment}[${idn}]` : segment });
57
+ }
58
+ for (const r of rs) {
59
+ if (usedR.has(r))
60
+ continue;
61
+ const idn = nodeIdentity(r) ?? "#pos";
62
+ pairs.push({ r, segment: segment && idn !== "#pos" ? `${segment}[${idn}]` : segment });
63
+ }
64
+ }
65
+ else {
66
+ const len = Math.max(ls.length, rs.length);
67
+ for (let i = 0; i < len; i++) {
68
+ pairs.push({ l: ls[i], r: rs[i], segment: multi && segment ? `${segment}[${i}]` : segment });
69
+ }
70
+ }
71
+ }
72
+ return pairs;
73
+ }
74
+ function compareElements(left, right, path, rows) {
75
+ if (!left && !right)
76
+ return;
77
+ if (!left) {
78
+ rows.push({ path, kind: "added" });
79
+ return;
80
+ }
81
+ if (!right) {
82
+ rows.push({ path, kind: "removed" });
83
+ return;
84
+ }
85
+ const leftAttrs = attributeChildren(left);
86
+ const rightAttrs = attributeChildren(right);
87
+ const ids = new Set([...leftAttrs.map((a) => a.attrs.id), ...rightAttrs.map((a) => a.attrs.id)]);
88
+ for (const id of ids) {
89
+ if (id === undefined || IGNORED_ATTRIBUTE_IDS.has(id))
90
+ continue;
91
+ const la = leftAttrs.find((a) => a.attrs.id === id);
92
+ const ra = rightAttrs.find((a) => a.attrs.id === id);
93
+ const attrPath = `${path}@${id}`;
94
+ if (!la) {
95
+ rows.push({ path: attrPath, kind: "added", right: ra.attrs.value });
96
+ continue;
97
+ }
98
+ if (!ra) {
99
+ rows.push({ path: attrPath, kind: "removed", left: la.attrs.value });
100
+ continue;
101
+ }
102
+ if (la.attrs.value !== ra.attrs.value) {
103
+ rows.push({ path: attrPath, kind: "changed", left: la.attrs.value, right: ra.attrs.value });
104
+ }
105
+ }
106
+ for (const tag of ["region", "node"]) {
107
+ const leftKids = structuralChildren(left, tag);
108
+ const rightKids = structuralChildren(right, tag);
109
+ if (leftKids.length === 0 && rightKids.length === 0)
110
+ continue;
111
+ const connector = path.split("@")[0].includes("region#") || tag === "node" ? "/" : ".";
112
+ for (const { l, r, segment } of pairSiblings(tag, leftKids, rightKids)) {
113
+ const isRootWrapper = tag === "node" && (l ?? r).attrs.id === "root";
114
+ const childPath = isRootWrapper || !segment ? path : `${path}${connector}${segment}`;
115
+ compareElements(l, r, childPath, rows);
116
+ }
117
+ }
118
+ }
119
+ export function diffLsx(leftXml, rightXml) {
120
+ const leftRoot = parseXml(leftXml);
121
+ const rightRoot = parseXml(rightXml);
122
+ const rootTag = leftRoot.tag !== "#document" ? leftRoot.tag : rightRoot.tag;
123
+ const rows = [];
124
+ compareElements(leftRoot, rightRoot, `$.${rootTag}`, rows);
125
+ return { rows, clean: rows.length === 0 };
126
+ }