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,389 @@
1
+ import * as crypto from "node:crypto";
2
+ import * as path from "node:path";
3
+ import { armBridge, bridgeStatus, pollForResult, resolveGrant, ALL_TIERS } from "../bridge/index.js";
4
+ import { executePlan, planFile, planMkdir } from "../safety/index.js";
5
+ import { checkRowProbes, rowProbeKinds, worstAttestation } from "./check.js";
6
+ import { loadTestPlans, resolveTestPlan, saveTestPlan } from "./load.js";
7
+ import { isChecklistOnly } from "./types.js";
8
+ import { applyClaimTransition, evidenceDir, loadClaims, saveClaims } from "../ledger/index.js";
9
+ const ROW_PROBE_SEPARATOR = "::";
10
+ const SETUP_MARKER = "setup::";
11
+ function prefixedProbeId(rowId, probeId, setup = false) {
12
+ return `${rowId}${ROW_PROBE_SEPARATOR}${setup ? SETUP_MARKER : ""}${probeId}`;
13
+ }
14
+ function splitPrefixedProbeId(id) {
15
+ const at = id.indexOf(ROW_PROBE_SEPARATOR);
16
+ if (at === -1)
17
+ return undefined;
18
+ const rowId = id.slice(0, at);
19
+ let rest = id.slice(at + ROW_PROBE_SEPARATOR.length);
20
+ let setup = false;
21
+ if (rest.startsWith(SETUP_MARKER)) {
22
+ setup = true;
23
+ rest = rest.slice(SETUP_MARKER.length);
24
+ }
25
+ return { rowId, probeId: rest, setup };
26
+ }
27
+ function selectRows(plan, rowIds, gates) {
28
+ let rows = plan.rows;
29
+ if (rowIds && rowIds.length > 0) {
30
+ const set = new Set(rowIds);
31
+ rows = rows.filter((r) => set.has(r.id));
32
+ }
33
+ if (gates && gates.length > 0) {
34
+ const set = new Set(gates);
35
+ rows = rows.filter((r) => set.has(r.gate));
36
+ }
37
+ return rows;
38
+ }
39
+ function verdictFromProbeResults(results) {
40
+ if (results.length === 0)
41
+ return "skipped";
42
+ if (results.some((r) => r.status === "error" || r.status === "unsupported"))
43
+ return "blocked";
44
+ if (results.some((r) => r.status === "skipped"))
45
+ return "skipped";
46
+ if (results.every((r) => r.status === "pass"))
47
+ return "verified";
48
+ return "contradicted";
49
+ }
50
+ function generateRunId(now) {
51
+ return `run-${now().getTime().toString(36)}-${crypto.randomBytes(4).toString("hex")}`;
52
+ }
53
+ function sha256(text) {
54
+ return crypto.createHash("sha256").update(text, "utf8").digest("hex");
55
+ }
56
+ const STALE_LAUNCH_ROUTE = {
57
+ baldursgate3: {
58
+ confirmedRoute: "Confirmed route: a Steam launch option of --skip-launcher, with a normal steam://rungameid/1086940 " +
59
+ "launch (a direct bg3.exe --skip-launcher invocation was also tried and works).",
60
+ readinessTest: "bridge status reports a fresh heartbeat once the game has reached the main menu or a save is loaded.",
61
+ },
62
+ cyberpunk2077: {
63
+ confirmedRoute: "No non-interactive launch route is confirmed for Cyberpunk 2077; start the game normally.",
64
+ readinessTest: "bridge status reports a fresh heartbeat once CET has finished loading (a save, or the main menu).",
65
+ },
66
+ };
67
+ function staleHeartbeatLaunchBlock(game, message) {
68
+ const route = STALE_LAUNCH_ROUTE[game];
69
+ return {
70
+ reason: "stale-heartbeat",
71
+ message: `run_tests refuses to arm on a stale heartbeat: ${message}`,
72
+ confirmedRoute: route.confirmedRoute,
73
+ readinessTest: route.readinessTest,
74
+ caveat: "ModWright never launches the game itself and never sends a reload command — start it by hand, then re-run run_tests.",
75
+ };
76
+ }
77
+ function alreadyVerified(row) {
78
+ return row.status === "verified";
79
+ }
80
+ export async function runTestPlan(input) {
81
+ const now = input.now ?? (() => new Date());
82
+ const runId = generateRunId(now);
83
+ const loaded = await resolveTestPlan(input.project, input.plan);
84
+ if (!loaded) {
85
+ const { plans } = await loadTestPlans(input.project);
86
+ throw new Error(`No test plan "${input.plan}" found. Known plan ids: ${plans.map((p) => p.plan.planId).join(", ") || "(none — check modwright.json's testPlans, or the default tests/*.yaml)"}`);
87
+ }
88
+ const { plan, path: planPath } = loaded;
89
+ const game = plan.game;
90
+ const selected = selectRows(plan, input.rows, input.gates);
91
+ const automatable = selected.filter((r) => !isChecklistOnly(r));
92
+ const manual = selected.filter((r) => isChecklistOnly(r));
93
+ const status = input.install
94
+ ? await bridgeStatus(game, input.install, input.surface, { now: input.now })
95
+ : { found: false, freshness: "absent", message: "No install was given, so bridge status could not be checked." };
96
+ const results = [];
97
+ const checklist = [];
98
+ let armed;
99
+ let refusedToArm;
100
+ let launchBlock;
101
+ let bridgeResult;
102
+ const rowRefusals = new Map();
103
+ const runnable = [];
104
+ for (const row of automatable) {
105
+ const refusals = checkRowProbes(row, game);
106
+ if (refusals.length > 0) {
107
+ rowRefusals.set(row.id, refusals);
108
+ }
109
+ else {
110
+ runnable.push(row);
111
+ }
112
+ }
113
+ const combinedProbes = [];
114
+ const overflowRowIds = new Set();
115
+ for (const row of runnable) {
116
+ const setupProbes = row.setup ?? [];
117
+ const rowProbes = row.probe.probes;
118
+ if (combinedProbes.length + setupProbes.length + rowProbes.length > 64) {
119
+ overflowRowIds.add(row.id);
120
+ continue;
121
+ }
122
+ for (const probe of setupProbes) {
123
+ combinedProbes.push({ ...probe, id: prefixedProbeId(row.id, probe.id, true) });
124
+ }
125
+ for (const probe of rowProbes) {
126
+ combinedProbes.push({ ...probe, id: prefixedProbeId(row.id, probe.id) });
127
+ }
128
+ }
129
+ const includedRunnable = runnable.filter((r) => !overflowRowIds.has(r.id));
130
+ const policyMode = input.policyMode ?? (input.allowMutate ? "workbench" : input.project.project.bridge?.policy?.mode ?? "verify");
131
+ const declaredGrant = input.project.project.bridge?.policy?.grant;
132
+ const anySetup = includedRunnable.some((r) => (r.setup?.length ?? 0) > 0);
133
+ const grantSet = new Set(resolveGrant(policyMode, declaredGrant));
134
+ if (input.allowMutate || anySetup)
135
+ grantSet.add("session");
136
+ const grant = ALL_TIERS.filter((t) => grantSet.has(t));
137
+ const deprecations = [];
138
+ if (input.allowMutate) {
139
+ deprecations.push('allowMutate is deprecated: it was mapped to grant:["session"]. Set bridge.policy.mode: workbench in modwright.json, or pass policyMode: "workbench", instead.');
140
+ }
141
+ const windowSeconds = Math.max(input.window?.seconds ?? 0, ...includedRunnable.map((r) => r.probe?.window?.seconds ?? 0), 0);
142
+ if (combinedProbes.length > 0 && input.mode === "apply") {
143
+ if (!input.install) {
144
+ refusedToArm = { reason: "no-install", message: "No install was given, so run_tests cannot arm the bridge." };
145
+ }
146
+ else if (status.found && status.freshness === "stale") {
147
+ refusedToArm = { reason: "stale-heartbeat", message: status.message };
148
+ launchBlock = staleHeartbeatLaunchBlock(game, status.message);
149
+ }
150
+ else {
151
+ const armResult = await armBridge({
152
+ game,
153
+ install: input.install,
154
+ surface: input.surface,
155
+ probes: combinedProbes,
156
+ grant,
157
+ window: windowSeconds > 0 ? { seconds: windowSeconds } : undefined,
158
+ plan: { planId: plan.planId, planPath, runId },
159
+ mode: "apply",
160
+ now: input.now,
161
+ });
162
+ if (armResult.refused) {
163
+ refusedToArm = armResult.refused;
164
+ }
165
+ else if (armResult.request && armResult.dir) {
166
+ armed = {
167
+ id: armResult.request.id,
168
+ nonce: armResult.request.nonce,
169
+ armedUntil: armResult.request.armedUntil,
170
+ probeCount: combinedProbes.length,
171
+ };
172
+ const outcome = await pollForResult({
173
+ game,
174
+ dirs: [armResult.dir],
175
+ id: armResult.request.id,
176
+ nonce: armResult.request.nonce,
177
+ probeCount: combinedProbes.length,
178
+ windowSeconds,
179
+ timeoutMs: input.timeoutSeconds ? input.timeoutSeconds * 1000 : undefined,
180
+ now: input.now,
181
+ sleep: input.sleep,
182
+ });
183
+ if (outcome.status === "complete") {
184
+ bridgeResult = outcome.result;
185
+ }
186
+ else {
187
+ refusedToArm = { reason: `poll-timeout-${outcome.reason}`, message: outcome.message };
188
+ }
189
+ }
190
+ }
191
+ }
192
+ else if (combinedProbes.length > 0) {
193
+ armed = {
194
+ id: `${runId}-preview`,
195
+ nonce: "0".repeat(16),
196
+ armedUntil: new Date(now().getTime() + 60_000).toISOString(),
197
+ probeCount: combinedProbes.length,
198
+ };
199
+ }
200
+ let evidencePath;
201
+ let evidenceSha256;
202
+ if (bridgeResult && input.mode === "apply") {
203
+ const dir = evidenceDir(input.project.root, runId);
204
+ const file = path.join(dir, "result.json");
205
+ const json = `${JSON.stringify(bridgeResult, null, 2)}\n`;
206
+ await executePlan({ purpose: `archive run_tests result ${runId}`, operations: [planMkdir(dir), planFile(file, json)], backupRoot: path.join(input.project.root, ".modwright", "backups") }, { mode: "apply", now: input.now, confidence: { level: "high", assumptions: [], verifiedBy: ["bridge result"] } });
207
+ evidencePath = path.relative(input.project.root, file).split(path.sep).join("/");
208
+ evidenceSha256 = sha256(json);
209
+ }
210
+ const probesByRow = new Map();
211
+ const setupByRow = new Map();
212
+ if (bridgeResult) {
213
+ for (const probeResult of bridgeResult.probes) {
214
+ const split = splitPrefixedProbeId(probeResult.id);
215
+ if (!split)
216
+ continue;
217
+ const target = split.setup ? setupByRow : probesByRow;
218
+ const list = target.get(split.rowId) ?? [];
219
+ list.push({ ...probeResult, id: split.probeId });
220
+ target.set(split.rowId, list);
221
+ }
222
+ }
223
+ const ledgerTransitions = [];
224
+ const loadedClaims = await loadClaims(input.project.root);
225
+ let claimsState = loadedClaims.claims;
226
+ const claimsFileExists = loadedClaims.exists;
227
+ for (const row of selected) {
228
+ if (isChecklistOnly(row)) {
229
+ results.push({ rowId: row.id, gate: row.gate, manual: true, probeStatuses: [], observed: row.observed, status: row.status, evidence: row.evidence, claim: row.claim });
230
+ if (!alreadyVerified(row)) {
231
+ checklist.push({
232
+ rowId: row.id,
233
+ gate: row.gate,
234
+ title: row.title,
235
+ preconditions: row.preconditions,
236
+ steps: row.steps,
237
+ expected: row.expected,
238
+ reason: "manual row",
239
+ });
240
+ }
241
+ continue;
242
+ }
243
+ const refusals = rowRefusals.get(row.id);
244
+ if (refusals && refusals.length > 0) {
245
+ results.push({ rowId: row.id, gate: row.gate, manual: false, probeStatuses: [], observed: undefined, status: "blocked", refusals, claim: row.claim });
246
+ checklist.push({
247
+ rowId: row.id,
248
+ gate: row.gate,
249
+ title: row.title,
250
+ preconditions: row.preconditions,
251
+ steps: row.steps,
252
+ expected: row.expected,
253
+ reason: `probe refused: ${refusals.map((r) => r.reason).join("; ")}`,
254
+ });
255
+ continue;
256
+ }
257
+ if (overflowRowIds.has(row.id)) {
258
+ results.push({ rowId: row.id, gate: row.gate, manual: false, probeStatuses: [], observed: undefined, status: "blocked", claim: row.claim });
259
+ checklist.push({
260
+ rowId: row.id,
261
+ gate: row.gate,
262
+ title: row.title,
263
+ preconditions: row.preconditions,
264
+ steps: row.steps,
265
+ expected: row.expected,
266
+ reason: "not run: this row's probes would push the combined request past the 64-probe protocol ceiling; re-run with fewer rows selected.",
267
+ });
268
+ continue;
269
+ }
270
+ if (input.mode !== "apply" || !bridgeResult) {
271
+ const reason = refusedToArm ? `bridge did not run: ${refusedToArm.message}` : "dry-run: not executed";
272
+ results.push({ rowId: row.id, gate: row.gate, manual: false, probeStatuses: [], observed: row.observed, status: row.status, claim: row.claim });
273
+ checklist.push({ rowId: row.id, gate: row.gate, title: row.title, preconditions: row.preconditions, steps: row.steps, expected: row.expected, reason });
274
+ continue;
275
+ }
276
+ const probeResults = probesByRow.get(row.id) ?? [];
277
+ const setupResults = setupByRow.get(row.id) ?? [];
278
+ const setupKinds = (row.setup ?? []).map((p) => p.kind);
279
+ const setupFailed = setupResults.some((r) => r.status !== "pass");
280
+ const verdict = setupFailed ? "blocked" : verdictFromProbeResults(probeResults);
281
+ const attestation = worstAttestation(rowProbeKinds(row));
282
+ const capped = verdict === "verified" && attestation === "unverified";
283
+ const finalStatus = capped ? "authored" : verdict;
284
+ const evidence = evidencePath && evidenceSha256 && (finalStatus === "verified" || finalStatus === "contradicted")
285
+ ? { runId, resultPath: evidencePath, resultSha256: evidenceSha256, observedAt: bridgeResult.finishedAt }
286
+ : row.evidence;
287
+ results.push({
288
+ rowId: row.id,
289
+ gate: row.gate,
290
+ manual: false,
291
+ probeStatuses: probeResults,
292
+ ...(setupResults.length > 0 ? { setupStatuses: setupResults } : {}),
293
+ observed: probeResults.map((p) => p.observed),
294
+ status: finalStatus,
295
+ evidence,
296
+ capped,
297
+ ...(setupKinds.length > 0 ? { verifiedWithSetup: setupKinds } : {}),
298
+ claim: row.claim,
299
+ });
300
+ if (finalStatus !== "verified") {
301
+ checklist.push({
302
+ rowId: row.id,
303
+ gate: row.gate,
304
+ title: row.title,
305
+ preconditions: row.preconditions,
306
+ steps: row.steps,
307
+ expected: row.expected,
308
+ reason: setupFailed
309
+ ? `setup did not complete: ${setupResults.filter((r) => r.status !== "pass").map((r) => `${r.id} ${r.status}`).join(", ")}`
310
+ : capped
311
+ ? `probe kind is unverified — a pass cannot promote past "authored" without human corroboration`
312
+ : `result: ${finalStatus}`,
313
+ });
314
+ }
315
+ if (row.claim && claimsFileExists && (finalStatus === "verified" || finalStatus === "contradicted") && evidence) {
316
+ const claimEvidence = {
317
+ runId,
318
+ planId: plan.planId,
319
+ rowId: row.id,
320
+ probeId: probeResults[0]?.id ?? row.id,
321
+ probeKind: rowProbeKinds(row)[0] ?? "unknown",
322
+ resultPath: evidence.resultPath,
323
+ resultSha256: evidence.resultSha256,
324
+ observedAt: evidence.observedAt,
325
+ observed: probeResults.map((p) => p.observed),
326
+ ...(setupKinds.length > 0 ? { verifiedWithSetup: setupKinds } : {}),
327
+ };
328
+ const existing = claimsState.find((c) => c.id === row.claim);
329
+ const transition = applyClaimTransition({
330
+ claims: claimsState,
331
+ id: row.claim,
332
+ to: finalStatus,
333
+ tool: "run_tests",
334
+ by: input.by ?? "run_tests",
335
+ now: input.now,
336
+ evidence: [claimEvidence],
337
+ });
338
+ ledgerTransitions.push({
339
+ claimId: row.claim,
340
+ from: existing?.status ?? "(new)",
341
+ to: finalStatus,
342
+ ok: transition.ok,
343
+ refusal: transition.refusal,
344
+ });
345
+ if (transition.ok)
346
+ claimsState = transition.claims;
347
+ }
348
+ }
349
+ if (ledgerTransitions.some((t) => t.ok) && input.mode === "apply") {
350
+ await saveClaims(input.project.root, claimsState, { mode: "apply", now: input.now });
351
+ }
352
+ if (input.mode === "apply") {
353
+ const byRowId = new Map(results.map((r) => [r.rowId, r]));
354
+ const updatedPlan = {
355
+ ...plan,
356
+ rows: plan.rows.map((row) => {
357
+ const result = byRowId.get(row.id);
358
+ if (!result)
359
+ return row;
360
+ return { ...row, observed: result.observed, status: result.status, evidence: result.evidence };
361
+ }),
362
+ };
363
+ await saveTestPlan(planPath, updatedPlan, { mode: "apply", now: input.now, backupRoot: path.join(input.project.root, ".modwright", "backups") });
364
+ }
365
+ const checklistLineCount = checklist.reduce((n, entry) => n + 1 + entry.preconditions.length + entry.steps.length, 0);
366
+ const next = input.mode === "dry-run"
367
+ ? `Nothing was armed. Re-run with { mode: "apply" } to arm the bridge for ${includedRunnable.length} automatable row(s) and print the same checklist.`
368
+ : refusedToArm
369
+ ? `run_tests did not complete: ${refusedToArm.message}`
370
+ : `Ran ${includedRunnable.length} automatable row(s); ${checklist.length} row(s) need the printed checklist.`;
371
+ return {
372
+ plan: { planId: plan.planId, path: planPath, title: plan.title, game },
373
+ runId,
374
+ policy: { mode: policyMode, grant },
375
+ ...(deprecations.length > 0 ? { deprecations } : {}),
376
+ rowsTotal: selected.length,
377
+ automatedCount: automatable.length,
378
+ manualCount: manual.length,
379
+ heartbeat: { found: status.found, freshness: status.freshness, message: status.message },
380
+ armed,
381
+ refusedToArm,
382
+ launchBlock,
383
+ results,
384
+ checklist,
385
+ checklistLineCount,
386
+ ledger: { transitions: ledgerTransitions },
387
+ next,
388
+ };
389
+ }
@@ -0,0 +1,82 @@
1
+ import { z } from "zod";
2
+ import { bridgeGameSchema, probeSchema } from "../bridge/types.js";
3
+ const iso8601Schema = z.string().refine((s) => !Number.isNaN(Date.parse(s)), {
4
+ message: "must be a parseable ISO 8601 timestamp",
5
+ });
6
+ export const testPlanRowStatusSchema = z.enum(["authored", "verified", "contradicted", "blocked", "skipped"]);
7
+ export const testPlanEvidenceSchema = z
8
+ .object({
9
+ runId: z.string().min(1),
10
+ resultPath: z.string().min(1),
11
+ resultSha256: z.string().regex(/^[0-9a-f]{64}$/, "must be a 64-character hex sha256"),
12
+ observedAt: iso8601Schema,
13
+ })
14
+ .strict();
15
+ export const testPlanProbeRequestSchema = z
16
+ .object({
17
+ window: z.object({ seconds: z.number().nonnegative() }).strict().optional(),
18
+ allowMutate: z.boolean().default(false),
19
+ probes: z.array(probeSchema).min(1).max(64),
20
+ })
21
+ .strict();
22
+ export const testPlanRowSchema = z
23
+ .object({
24
+ id: z.string().min(1),
25
+ gate: z.string().min(1),
26
+ title: z.string().min(1),
27
+ preconditions: z.array(z.string().min(1)).default([]),
28
+ steps: z.array(z.string().min(1)).default([]),
29
+ expected: z.string().min(1),
30
+ probe: testPlanProbeRequestSchema.optional(),
31
+ setup: z.array(probeSchema).min(1).max(64).optional(),
32
+ manual: z.boolean().default(false),
33
+ claim: z.string().min(1).optional(),
34
+ observed: z.unknown().optional(),
35
+ status: testPlanRowStatusSchema.default("authored"),
36
+ evidence: testPlanEvidenceSchema.optional(),
37
+ })
38
+ .strict();
39
+ export const testPlanFixtureSchema = z
40
+ .object({
41
+ save: z.string().optional(),
42
+ notes: z.string().optional(),
43
+ })
44
+ .strict();
45
+ export const testPlanSchema = z
46
+ .object({
47
+ planId: z.string().min(1),
48
+ title: z.string().min(1),
49
+ game: bridgeGameSchema,
50
+ fixture: testPlanFixtureSchema.optional(),
51
+ rows: z.array(testPlanRowSchema).min(1),
52
+ })
53
+ .strict();
54
+ export function isChecklistOnly(row) {
55
+ return row.manual || !row.probe;
56
+ }
57
+ function formatPath(segments) {
58
+ if (segments.length === 0)
59
+ return "(root)";
60
+ let out = "";
61
+ for (const segment of segments) {
62
+ out += typeof segment === "number" ? `[${segment}]` : out ? `.${segment}` : segment;
63
+ }
64
+ return out;
65
+ }
66
+ export function parseTestPlan(raw) {
67
+ const result = testPlanSchema.safeParse(raw);
68
+ if (result.success)
69
+ return result.data;
70
+ const rawRows = Array.isArray(raw?.rows) ? (raw.rows) : [];
71
+ const lines = result.error.issues.map((issue) => {
72
+ const [first, second, ...rest] = issue.path;
73
+ if (first === "rows" && typeof second === "number") {
74
+ const rawRow = rawRows[second];
75
+ const rowId = typeof rawRow?.id === "string" ? rawRow.id : `#${second}`;
76
+ const field = rest.length > 0 ? ` ${formatPath(rest)}` : "";
77
+ return `rows[${second}] (id=${rowId})${field}: ${issue.message}`;
78
+ }
79
+ return `${formatPath(issue.path)}: ${issue.message}`;
80
+ });
81
+ throw new Error(`Invalid test plan:\n${lines.join("\n")}`);
82
+ }
@@ -0,0 +1,50 @@
1
+ function argv(action, sourceAbs, destAbs) {
2
+ return ["-g", "bg3", "-a", action, "-s", sourceAbs, "-d", destAbs];
3
+ }
4
+ export const LSX_TO_LSF = {
5
+ id: "bg3.lsx-to-lsf",
6
+ surface: "baldursgate3",
7
+ from: ".lsx",
8
+ to: ".lsf",
9
+ toolId: "divine",
10
+ inverse: "bg3.lsf-to-lsx",
11
+ roundTrip: "reverse-and-field-diff",
12
+ diffKind: "lsx",
13
+ argv: (s, d) => argv("convert-resource", s, d),
14
+ describe: (s, d) => `divine -g bg3 -a convert-resource -s ${s} -d ${d}`,
15
+ };
16
+ export const LSF_TO_LSX = {
17
+ id: "bg3.lsf-to-lsx",
18
+ surface: "baldursgate3",
19
+ from: ".lsf",
20
+ to: ".lsx",
21
+ toolId: "divine",
22
+ inverse: "bg3.lsx-to-lsf",
23
+ roundTrip: "forward-and-byte-compare",
24
+ argv: (s, d) => argv("convert-resource", s, d),
25
+ describe: (s, d) => `divine -g bg3 -a convert-resource -s ${s} -d ${d}`,
26
+ };
27
+ export const XML_TO_LOCA = {
28
+ id: "bg3.xml-to-loca",
29
+ surface: "baldursgate3",
30
+ from: ".xml",
31
+ to: ".loca",
32
+ toolId: "divine",
33
+ inverse: "bg3.loca-to-xml",
34
+ roundTrip: "reverse-and-field-diff",
35
+ diffKind: "loca-xml",
36
+ argv: (s, d) => argv("convert-loca", s, d),
37
+ describe: (s, d) => `divine -g bg3 -a convert-loca -s ${s} -d ${d}`,
38
+ };
39
+ export const LOCA_TO_XML = {
40
+ id: "bg3.loca-to-xml",
41
+ surface: "baldursgate3",
42
+ from: ".loca",
43
+ to: ".xml",
44
+ toolId: "divine",
45
+ inverse: "bg3.xml-to-loca",
46
+ roundTrip: "forward-and-byte-compare",
47
+ argv: (s, d) => argv("convert-loca", s, d),
48
+ describe: (s, d) => `divine -g bg3 -a convert-loca -s ${s} -d ${d}`,
49
+ };
50
+ export const BG3_CONVERTERS = [LSX_TO_LSF, LSF_TO_LSX, XML_TO_LOCA, LOCA_TO_XML];
@@ -0,0 +1,36 @@
1
+ import * as path from "node:path";
2
+ function argv(action, sourceAbs, destDirAbs) {
3
+ return ["convert", action, sourceAbs, "-o", destDirAbs];
4
+ }
5
+ function expectedOutputName(action, sourceAbs) {
6
+ const base = path.basename(sourceAbs);
7
+ return action === "serialize" ? `${base}.json` : base.replace(/\.json$/i, "");
8
+ }
9
+ export const CR2W_TO_JSON = {
10
+ id: "cp2077.cr2w-to-json",
11
+ surface: "cyberpunk2077",
12
+ from: "CR2W",
13
+ to: ".json",
14
+ toolId: "wolvenkit-cli",
15
+ inverse: "cp2077.json-to-cr2w",
16
+ roundTrip: "forward-and-byte-compare",
17
+ argv: (s, d) => argv("serialize", s, d),
18
+ describe: (s, d) => `WolvenKit.CLI convert serialize ${s} -o ${d}`,
19
+ destinationKind: "output-directory",
20
+ expectedOutputName: (s) => expectedOutputName("serialize", s),
21
+ };
22
+ export const JSON_TO_CR2W = {
23
+ id: "cp2077.json-to-cr2w",
24
+ surface: "cyberpunk2077",
25
+ from: ".json",
26
+ to: "CR2W",
27
+ toolId: "wolvenkit-cli",
28
+ inverse: "cp2077.cr2w-to-json",
29
+ roundTrip: "reverse-and-field-diff",
30
+ diffKind: "cr2w-json",
31
+ argv: (s, d) => argv("deserialize", s, d),
32
+ describe: (s, d) => `WolvenKit.CLI convert deserialize ${s} -o ${d}`,
33
+ destinationKind: "output-directory",
34
+ expectedOutputName: (s) => expectedOutputName("deserialize", s),
35
+ };
36
+ export const CYBERPUNK_CONVERTERS = [CR2W_TO_JSON, JSON_TO_CR2W];