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,100 @@
1
+ import { existsSync, promises as fs } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+ import * as path from "node:path";
4
+ import { isDirectory, pathExists } from "../fsutil.js";
5
+ import { parseProject } from "../project/schema.js";
6
+ import { PROJECT_FILE } from "../project/types.js";
7
+ import { executePlan, planCopy, planFile, planMkdir } from "../safety/index.js";
8
+ const BRIDGE_DIR_BY_GAME = {
9
+ baldursgate3: "bg3-se",
10
+ cyberpunk2077: "cp2077-cet",
11
+ };
12
+ export function bridgeId(game) {
13
+ return BRIDGE_DIR_BY_GAME[game];
14
+ }
15
+ function findPackageRoot(startDir) {
16
+ let dir = startDir;
17
+ for (;;) {
18
+ if (existsSync(path.join(dir, "package.json")))
19
+ return dir;
20
+ const parent = path.dirname(dir);
21
+ if (parent === dir)
22
+ throw new Error(`bridge: could not find package.json above ${startDir}`);
23
+ dir = parent;
24
+ }
25
+ }
26
+ export function bridgesRootDir() {
27
+ const here = path.dirname(fileURLToPath(import.meta.url));
28
+ return path.join(findPackageRoot(here), "bridges");
29
+ }
30
+ export function bridgeSourceDir(game) {
31
+ return path.join(bridgesRootDir(), bridgeId(game));
32
+ }
33
+ export function stagedBridgeDir(projectRoot, game) {
34
+ return path.join(projectRoot, ".modwright", "bridge", bridgeId(game));
35
+ }
36
+ export async function bridgeDeployTarget(game) {
37
+ const manifestPath = path.join(bridgeSourceDir(game), "bridge.json");
38
+ const manifest = JSON.parse(await fs.readFile(manifestPath, "utf8"));
39
+ const d = manifest.deploy;
40
+ if (!d || typeof d.rootId !== "string" || typeof d.from !== "string" || typeof d.into !== "string" || typeof d.variant !== "string") {
41
+ throw new Error(`bridge stage: ${manifestPath} has no complete "deploy" block (rootId, from, into, variant).`);
42
+ }
43
+ const target = { rootId: d.rootId, from: d.from, into: d.into, variant: d.variant };
44
+ if (typeof d.locked === "boolean")
45
+ target.locked = d.locked;
46
+ if (typeof d.backup === "boolean")
47
+ target.backup = d.backup;
48
+ if (Array.isArray(d.prune))
49
+ target.prune = d.prune.filter((p) => typeof p === "string");
50
+ return target;
51
+ }
52
+ async function planDeployTargetRegistration(projectRoot, game) {
53
+ const projectFile = path.join(projectRoot, PROJECT_FILE);
54
+ if (!(await pathExists(projectFile)))
55
+ return { outcome: "no-project" };
56
+ const target = await bridgeDeployTarget(game);
57
+ const json = JSON.parse(await fs.readFile(projectFile, "utf8"));
58
+ const deploy = (json.deploy ?? { targets: [] });
59
+ const targets = (deploy.targets ?? []);
60
+ const present = targets.some((t) => t.rootId === target.rootId && (t.into ?? undefined) === target.into && (t.variant ?? undefined) === target.variant);
61
+ if (present)
62
+ return { outcome: "present" };
63
+ deploy.targets = [...targets, target];
64
+ json.deploy = deploy;
65
+ try {
66
+ parseProject(json);
67
+ }
68
+ catch (err) {
69
+ throw new Error(`bridge stage: adding the ${bridgeId(game)} deploy target would leave modwright.json invalid: ${err.message}`);
70
+ }
71
+ return { op: planFile(projectFile, `${JSON.stringify(json, null, 2)}\n`), outcome: "added" };
72
+ }
73
+ export async function planStageBridge(projectRoot, game) {
74
+ const from = bridgeSourceDir(game);
75
+ if (!(await isDirectory(from))) {
76
+ throw new Error(`bridge stage: ${from} does not exist yet — bridges/${bridgeId(game)}/ has no bridge content on disk to stage.`);
77
+ }
78
+ const to = stagedBridgeDir(projectRoot, game);
79
+ const registration = await planDeployTargetRegistration(projectRoot, game);
80
+ const operations = [planMkdir(path.dirname(to)), planCopy(from, to)];
81
+ if (registration.op)
82
+ operations.push(registration.op);
83
+ return {
84
+ purpose: registration.outcome === "added"
85
+ ? `stage the ${bridgeId(game)} bridge into .modwright/bridge/${bridgeId(game)} and register its withBridge deploy target`
86
+ : `stage the ${bridgeId(game)} bridge into .modwright/bridge/${bridgeId(game)}`,
87
+ operations,
88
+ backupRoot: path.join(projectRoot, ".modwright", "backups"),
89
+ deployTarget: registration.outcome,
90
+ };
91
+ }
92
+ export async function stageBridge(projectRoot, game, options = {}) {
93
+ const { deployTarget, ...plan } = await planStageBridge(projectRoot, game);
94
+ const report = await executePlan({ ...plan, backupRoot: path.join(projectRoot, ".modwright", "backups") }, {
95
+ mode: options.mode ?? "dry-run",
96
+ now: options.now,
97
+ confidence: { level: "high", assumptions: [], verifiedBy: [`bridges/${bridgeId(game)}/ on disk`] },
98
+ });
99
+ return { ...report, deployTarget };
100
+ }
@@ -0,0 +1,97 @@
1
+ import { CET_LOG_HEARTBEAT_FRESH_MS, cetLogCandidates, cetSandboxIo, readCetHeartbeat } from "./cet-log.js";
2
+ import { HEARTBEAT_FRESH_MS, locateBridge } from "./locate.js";
3
+ import { PROTOCOL_VERSION } from "./types.js";
4
+ export async function bridgeStatus(game, install, surface, deps = {}) {
5
+ if (game === "cyberpunk2077")
6
+ return cetLogStatus(install, surface, deps);
7
+ const located = await locateBridge(game, install, surface, deps);
8
+ if (!located.heartbeat) {
9
+ return {
10
+ game,
11
+ transport: "file",
12
+ candidates: located.candidates,
13
+ found: false,
14
+ freshness: "absent",
15
+ expectedProtocolVersion: PROTOCOL_VERSION,
16
+ message: "No heartbeat found in any candidate directory — the bridge is not deployed, or the game has not run since deploy.",
17
+ };
18
+ }
19
+ const ageMs = located.ageMs ?? Number.POSITIVE_INFINITY;
20
+ const fresh = ageMs <= HEARTBEAT_FRESH_MS;
21
+ const protocolVersionMatch = located.heartbeat.protocolVersion === PROTOCOL_VERSION;
22
+ const message = !protocolVersionMatch
23
+ ? `Heartbeat reports protocolVersion ${located.heartbeat.protocolVersion}, ModWright expects ${PROTOCOL_VERSION}. ` +
24
+ `Re-run "bridge stage", then "deploy --variant withBridge".`
25
+ : fresh
26
+ ? "Bridge heartbeat is fresh and protocol versions match."
27
+ : `Heartbeat is stale (${Math.round(ageMs / 1000)}s old) — the game is probably not running, or the bridge crashed. Check the SE/CET log.`;
28
+ return {
29
+ game,
30
+ transport: "file",
31
+ candidates: located.candidates,
32
+ found: true,
33
+ dir: located.dir,
34
+ heartbeat: located.heartbeat,
35
+ ageMs: located.ageMs,
36
+ freshness: fresh ? "fresh" : "stale",
37
+ expectedProtocolVersion: PROTOCOL_VERSION,
38
+ protocolVersionMatch,
39
+ message,
40
+ };
41
+ }
42
+ async function cetLogStatus(install, surface, deps) {
43
+ const logCandidates = await cetLogCandidates(install, surface);
44
+ const now = deps.now ?? (() => new Date());
45
+ const located = await locateBridge("cyberpunk2077", install, surface, deps);
46
+ const fromLog = await readCetHeartbeat(logCandidates, { now: deps.now });
47
+ const useFile = located.heartbeat !== undefined &&
48
+ (fromLog === undefined || Date.parse(located.heartbeat.at) >= Date.parse(fromLog.heartbeat.at));
49
+ const candidates = [...located.candidates, ...logCandidates];
50
+ if (!located.heartbeat && !fromLog) {
51
+ return {
52
+ game: "cyberpunk2077",
53
+ transport: "log",
54
+ candidates,
55
+ found: false,
56
+ freshness: "absent",
57
+ expectedProtocolVersion: PROTOCOL_VERSION,
58
+ message: `No heartbeat file in ${located.candidates.join(" or ")} and no [MODWRIGHT-HEARTBEAT] line in ` +
59
+ `${logCandidates.join(" or ")} — the bridge is not deployed, the game has not run since deploy, or its output ` +
60
+ `lands somewhere none of those cover. Read the bridge's own m.where() capability line to tell those apart.`,
61
+ };
62
+ }
63
+ const heartbeat = useFile ? located.heartbeat : fromLog.heartbeat;
64
+ const transport = useFile ? "file" : "log";
65
+ const where = useFile ? located.dir : fromLog.file;
66
+ const freshMs = useFile ? HEARTBEAT_FRESH_MS : CET_LOG_HEARTBEAT_FRESH_MS;
67
+ const ageMs = now().getTime() - Date.parse(heartbeat.at);
68
+ const fresh = ageMs <= freshMs;
69
+ const protocolVersionMatch = heartbeat.protocolVersion === PROTOCOL_VERSION;
70
+ const sandboxIo = cetSandboxIo(heartbeat);
71
+ const ioNote = sandboxIo === true
72
+ ? `The bridge's own sandbox HAS io (capability line), so the file transport is live.`
73
+ : sandboxIo === false
74
+ ? `The bridge's own sandbox has NO io (capability line), so it is running log-only and results come back as [MODWRIGHT-RESULT] lines.`
75
+ :
76
+ `The heartbeat does not say whether the bridge's sandbox has io; run m.where() in the CET console to find out.`;
77
+ const message = !protocolVersionMatch
78
+ ? `The heartbeat (${transport} transport, ${where}) reports protocolVersion ${heartbeat.protocolVersion}, ModWright expects ${PROTOCOL_VERSION}. ` +
79
+ `Re-run "bridge stage", then "deploy --variant withBridge".`
80
+ : fresh
81
+ ? `Bridge heartbeat is fresh (${transport} transport, ${where}) and protocol versions match. ${ioNote}`
82
+ : `The newest heartbeat (${transport} transport, ${where}) is ${Math.round(ageMs / 1000)}s old — the game is probably not running, or the bridge stopped ticking. ${ioNote}`;
83
+ return {
84
+ game: "cyberpunk2077",
85
+ transport,
86
+ candidates,
87
+ found: true,
88
+ ...(useFile ? { dir: located.dir } : { logFile: fromLog.file }),
89
+ ...(sandboxIo === undefined ? {} : { sandboxIo }),
90
+ heartbeat,
91
+ ageMs,
92
+ freshness: fresh ? "fresh" : "stale",
93
+ expectedProtocolVersion: PROTOCOL_VERSION,
94
+ protocolVersionMatch,
95
+ message,
96
+ };
97
+ }
@@ -0,0 +1,145 @@
1
+ import { z } from "zod";
2
+ export const PROTOCOL_VERSION = 1;
3
+ export const BRIDGE_BANNER = "MODWRIGHT-BRIDGE-DO-NOT-SHIP";
4
+ export const bridgeGameSchema = z.enum(["baldursgate3", "cyberpunk2077"]);
5
+ export const bridgeTierSchema = z.enum(["read", "observe", "session", "persist", "process"]);
6
+ export const ALL_TIERS = ["read", "observe", "session", "persist", "process"];
7
+ export const MUTATION_TIER = "session";
8
+ export function resolveGrant(mode, declared) {
9
+ const set = new Set(mode === "workbench" ? ["read", "observe", "session"] : ["read", "observe"]);
10
+ for (const tier of declared ?? [])
11
+ set.add(tier);
12
+ return ALL_TIERS.filter((tier) => set.has(tier));
13
+ }
14
+ export function grantAllowsMutation(grant, allowMutate) {
15
+ if (grant && grant.includes(MUTATION_TIER))
16
+ return true;
17
+ return allowMutate === true;
18
+ }
19
+ export const probeContextSchema = z.enum(["server", "client", "cet"]);
20
+ export const probeEffectSchema = z.enum(["read", "observe", "mutate"]);
21
+ export const probeExpectOpSchema = z.enum(["eq", "ne", "gte", "lte", "contains", "absent"]);
22
+ export const probeExpectSchema = z
23
+ .object({
24
+ op: probeExpectOpSchema,
25
+ value: z.unknown(),
26
+ })
27
+ .strict();
28
+ export const probeSchema = z
29
+ .object({
30
+ id: z.string().min(1),
31
+ kind: z.string().min(1),
32
+ context: probeContextSchema,
33
+ effect: probeEffectSchema,
34
+ params: z.record(z.unknown()),
35
+ expect: probeExpectSchema.optional(),
36
+ settleSeconds: z.number().nonnegative().optional(),
37
+ note: z.string().optional(),
38
+ })
39
+ .strict();
40
+ const iso8601Schema = z.string().refine((s) => !Number.isNaN(Date.parse(s)), {
41
+ message: "must be a parseable ISO 8601 timestamp",
42
+ });
43
+ export const REQUEST_ID_PATTERN = /^[a-z0-9-]{8,64}$/;
44
+ export const NONCE_PATTERN = /^[0-9a-f]{16}$/;
45
+ export const bridgePlanRefSchema = z
46
+ .object({
47
+ planId: z.string().min(1),
48
+ planPath: z.string().min(1),
49
+ runId: z.string().min(1),
50
+ })
51
+ .strict();
52
+ export const bridgeRequestSchema = z
53
+ .object({
54
+ protocolVersion: z.literal(PROTOCOL_VERSION),
55
+ id: z.string().regex(REQUEST_ID_PATTERN, `must match ${REQUEST_ID_PATTERN}`),
56
+ nonce: z.string().regex(NONCE_PATTERN, `must match ${NONCE_PATTERN} (16 hex characters)`),
57
+ issuedAt: iso8601Schema,
58
+ armedUntil: iso8601Schema,
59
+ game: bridgeGameSchema,
60
+ allowMutate: z.boolean().default(false),
61
+ grant: z.array(bridgeTierSchema).optional(),
62
+ window: z.object({ seconds: z.number().nonnegative() }).strict().optional(),
63
+ plan: bridgePlanRefSchema.optional(),
64
+ probes: z.array(probeSchema).min(1).max(64),
65
+ })
66
+ .strict();
67
+ export const bridgeMarkerSchema = z
68
+ .object({
69
+ protocolVersion: z.literal(PROTOCOL_VERSION),
70
+ id: z.string().min(1),
71
+ nonce: z.string().min(1),
72
+ bytes: z.number().int().nonnegative(),
73
+ })
74
+ .strict();
75
+ export const probeResultStatusSchema = z.enum(["pass", "fail", "error", "skipped", "unsupported"]);
76
+ export const probeResultSchema = z
77
+ .object({
78
+ id: z.string().min(1),
79
+ kind: z.string().min(1),
80
+ status: probeResultStatusSchema,
81
+ observed: z.unknown(),
82
+ evidence: z.record(z.unknown()),
83
+ error: z.string().optional(),
84
+ ranAt: iso8601Schema,
85
+ })
86
+ .strict();
87
+ export const disarmReasonSchema = z.enum([
88
+ "expired",
89
+ "nonce-mismatch",
90
+ "already-consumed",
91
+ "protocol-mismatch",
92
+ "mutation-not-allowed",
93
+ "wrong-game",
94
+ ]);
95
+ export const bridgeInfoSchema = z
96
+ .object({
97
+ name: z.string().min(1),
98
+ version: z.string().min(1),
99
+ protocolVersion: z.number().int(),
100
+ context: z.string().min(1),
101
+ runtimeVersion: z.string().optional(),
102
+ gameVersion: z.string().optional(),
103
+ })
104
+ .strict();
105
+ export const bridgeResultSchema = z
106
+ .object({
107
+ protocolVersion: z.number().int(),
108
+ id: z.string().min(1),
109
+ nonce: z.string().min(1),
110
+ startedAt: iso8601Schema,
111
+ finishedAt: iso8601Schema,
112
+ complete: z.literal(true),
113
+ bridge: bridgeInfoSchema,
114
+ degraded: z.object({ osiris: z.boolean(), reason: z.string() }).strict().optional(),
115
+ disarm: z.object({ reason: disarmReasonSchema, detail: z.string() }).strict().optional(),
116
+ probes: z.array(probeResultSchema),
117
+ errors: z.array(z.string()),
118
+ })
119
+ .strict();
120
+ export const bridgeDoneSchema = z
121
+ .object({
122
+ protocolVersion: z.number().int(),
123
+ id: z.string().min(1),
124
+ nonce: z.string().min(1),
125
+ bytes: z.number().int().nonnegative(),
126
+ probeCount: z.number().int().nonnegative(),
127
+ })
128
+ .strict();
129
+ export const bridgeHeartbeatStateSchema = z.enum(["idle", "running", "disarmed"]);
130
+ export const bridgeHeartbeatSchema = z
131
+ .object({
132
+ protocolVersion: z.number().int(),
133
+ bridge: bridgeInfoSchema,
134
+ at: iso8601Schema,
135
+ uptimeMs: z.number().nonnegative(),
136
+ state: bridgeHeartbeatStateSchema,
137
+ lastRequestId: z.string().optional(),
138
+ lastResultId: z.string().optional(),
139
+ writeRoot: z.string(),
140
+ writeRootErrors: z.array(z.string()).optional(),
141
+ osirisDegraded: z.boolean().optional(),
142
+ gameState: z.string().optional(),
143
+ commandLine: z.array(z.string()).optional(),
144
+ })
145
+ .strict();
@@ -0,0 +1,107 @@
1
+ import { promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ import { walk } from "../fsutil.js";
4
+ import { BRIDGE_BANNER } from "./types.js";
5
+ const GROUPS = ["history"];
6
+ const MOD_TYPES = ["packaging"];
7
+ function toPosix(p) {
8
+ return p.split(path.sep).join("/");
9
+ }
10
+ function pathNamesBridge(relPosix) {
11
+ const lower = relPosix.toLowerCase();
12
+ return /(^|\/)modwrightbridge(\/|$)/.test(lower) || lower.includes(".modwright/bridge/");
13
+ }
14
+ const BANNER_SCAN_EXTENSIONS = new Set([".lua", ".json", ".txt", ".md"]);
15
+ function extensionOf(p) {
16
+ const idx = p.lastIndexOf(".");
17
+ return idx === -1 ? "" : p.slice(idx).toLowerCase();
18
+ }
19
+ async function readFirstKB(absPath) {
20
+ let handle;
21
+ try {
22
+ handle = await fs.open(absPath, "r");
23
+ }
24
+ catch {
25
+ return "";
26
+ }
27
+ try {
28
+ const buf = Buffer.alloc(1024);
29
+ const { bytesRead } = await handle.read(buf, 0, buf.length, 0);
30
+ return buf.subarray(0, bytesRead).toString("utf8");
31
+ }
32
+ catch {
33
+ return "";
34
+ }
35
+ finally {
36
+ await handle.close();
37
+ }
38
+ }
39
+ function isProjectTarget(target) {
40
+ return target.kind === "project";
41
+ }
42
+ export function bridgeNeverShipValidator(opts) {
43
+ return {
44
+ id: opts.id,
45
+ game: opts.game,
46
+ groups: GROUPS,
47
+ modTypes: MOD_TYPES,
48
+ summary: "Blocks a build whose build.outDir or build.staging already contains the ModWright bridge — a " +
49
+ "ModWrightBridge/ path, a .modwright/bridge/ path, or the bridge's content banner in a file's first KB. " +
50
+ "The bridge is author-only and must never reach a dist build.",
51
+ appliesTo: isProjectTarget,
52
+ appliesToDescription: "the whole project: build.outDir and build.staging, when they already exist on disk",
53
+ needs: { project: true },
54
+ cost: "build",
55
+ severity: "block",
56
+ evidence: opts.evidence,
57
+ async run(ctx) {
58
+ const project = ctx.project;
59
+ const findings = [];
60
+ const roots = [];
61
+ if (project.build?.outDirExists && project.build.absOutDir) {
62
+ roots.push({ label: "build.outDir", abs: project.build.absOutDir });
63
+ }
64
+ if (project.build?.absStaging) {
65
+ roots.push({ label: "build.staging", abs: project.build.absStaging });
66
+ }
67
+ for (const root of roots) {
68
+ const { files, truncated } = await walk(root.abs, { limit: 50_000 });
69
+ for (const rel of files) {
70
+ const relPosix = toPosix(rel);
71
+ const abs = path.join(root.abs, rel);
72
+ const pathHit = pathNamesBridge(relPosix);
73
+ let bannerHit = false;
74
+ if (!pathHit && BANNER_SCAN_EXTENSIONS.has(extensionOf(rel))) {
75
+ bannerHit = (await readFirstKB(abs)).includes(BRIDGE_BANNER);
76
+ }
77
+ if (!pathHit && !bannerHit)
78
+ continue;
79
+ findings.push({
80
+ validatorId: opts.id,
81
+ severity: "block",
82
+ file: abs,
83
+ message: pathHit
84
+ ?
85
+ `"${relPosix}" is under ${root.label} (${root.abs}) and is part of the ModWright bridge. The bridge ` +
86
+ `is author-only and must never reach a dist build.`
87
+ : `"${relPosix}" is under ${root.label} (${root.abs}) and carries the ModWright bridge's content banner ` +
88
+ `in its first KB, even though its path does not name the bridge folder — it looks like bridge ` +
89
+ `content that reached the build output under a different path (e.g. flattened during packaging).`,
90
+ fix: `Remove ".modwright/bridge/" from this build's sources and re-run without --variant withBridge, or rebuild after "deploy" (no variant) has pruned it back out.`,
91
+ evidence: opts.evidence,
92
+ });
93
+ }
94
+ if (truncated) {
95
+ findings.push({
96
+ validatorId: opts.id,
97
+ severity: "info",
98
+ file: root.abs,
99
+ message: `${root.label} has more files than this scan's limit; the bridge-never-ships scan may be incomplete for it.`,
100
+ evidence: opts.evidence,
101
+ });
102
+ }
103
+ }
104
+ return findings;
105
+ },
106
+ };
107
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./types.js";
2
+ export { runBuild } from "./run.js";
3
+ export { isGuardedPath } from "./stage.js";
4
+ export { crc32, createZip } from "./zip.js";
5
+ export { directoryEntry, fileEntry, pathEntries, pathEntry, sha256Bytes, sha256File } from "./manifest.js";
6
+ export { resolveOutDirPath, resolveSourceOrStagePath, resolveStageId, stageStepOutputDir, } from "./paths.js";
@@ -0,0 +1,47 @@
1
+ import { createHash } from "node:crypto";
2
+ import { createReadStream, promises as fs } from "node:fs";
3
+ import * as path from "node:path";
4
+ import { isDirectory, walk } from "../fsutil.js";
5
+ export function sha256File(absPath) {
6
+ return new Promise((resolve, reject) => {
7
+ const hash = createHash("sha256");
8
+ const stream = createReadStream(absPath);
9
+ stream.on("data", (chunk) => hash.update(chunk));
10
+ stream.on("end", () => resolve(hash.digest("hex")));
11
+ stream.on("error", reject);
12
+ });
13
+ }
14
+ export function sha256Bytes(data) {
15
+ return createHash("sha256").update(data).digest("hex");
16
+ }
17
+ function toPosix(p) {
18
+ return p.split(path.sep).join("/");
19
+ }
20
+ export async function fileEntry(absPath) {
21
+ const stat = await fs.stat(absPath);
22
+ return { path: absPath, sha256: await sha256File(absPath), sizeBytes: stat.size };
23
+ }
24
+ export async function directoryEntry(absDir) {
25
+ const walked = await walk(absDir, { limit: 200_000 });
26
+ const files = [];
27
+ let totalBytes = 0;
28
+ for (const rel of walked.files) {
29
+ const abs = path.join(absDir, rel);
30
+ const stat = await fs.stat(abs);
31
+ const sha256 = await sha256File(abs);
32
+ files.push({ path: toPosix(rel), sha256, sizeBytes: stat.size });
33
+ totalBytes += stat.size;
34
+ }
35
+ files.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
36
+ const treeSha256 = sha256Bytes(Buffer.from(files.map((f) => `${f.path}:${f.sha256}`).join("\n"), "utf8"));
37
+ return { path: absDir, sha256: treeSha256, sizeBytes: totalBytes, files, treeSha256 };
38
+ }
39
+ export async function pathEntry(absPath) {
40
+ return (await isDirectory(absPath)) ? directoryEntry(absPath) : fileEntry(absPath);
41
+ }
42
+ export async function pathEntries(absPaths) {
43
+ const out = [];
44
+ for (const p of absPaths)
45
+ out.push(await pathEntry(p));
46
+ return out;
47
+ }
@@ -0,0 +1,32 @@
1
+ import * as path from "node:path";
2
+ function substituteVersion(raw, version) {
3
+ return raw.split("${version}").join(version);
4
+ }
5
+ function substituteStage(raw, ctx) {
6
+ return substituteVersion(raw.split("${stage}").join(ctx.stageRoot), ctx.version);
7
+ }
8
+ export function resolveSourceOrStagePath(raw, ctx) {
9
+ if (raw.includes("${stage}"))
10
+ return substituteStage(raw, ctx);
11
+ const withVersion = substituteVersion(raw, ctx.version);
12
+ return path.isAbsolute(withVersion) ? withVersion : path.join(ctx.projectRoot, withVersion);
13
+ }
14
+ export function resolveOutDirPath(raw, ctx) {
15
+ const withVersion = substituteVersion(raw, ctx.version);
16
+ if (withVersion.includes("${stage}"))
17
+ return substituteStage(withVersion, ctx);
18
+ return path.isAbsolute(withVersion) ? withVersion : path.join(ctx.outDirAbs, withVersion);
19
+ }
20
+ export function resolveStageId(raw, ctx) {
21
+ if (!raw)
22
+ return ctx.stageRoot;
23
+ if (raw.includes("${stage}"))
24
+ return substituteStage(raw, ctx);
25
+ return path.join(ctx.stageRoot, substituteVersion(raw, ctx.version));
26
+ }
27
+ export function stageStepOutputDir(stageRoot, id, into) {
28
+ return into ? path.join(stageRoot, id, into) : path.join(stageRoot, id);
29
+ }
30
+ export function resolvedToolNote(location) {
31
+ return location.found && location.path ? ` [runs ${location.path}, from ${location.source ?? "unknown"}]` : " [tool not located]";
32
+ }