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,190 @@
1
+ import * as path from "node:path";
2
+ import { readPeFileVersion } from "../../core/compat/pe.js";
3
+ import { parseVersion } from "../../core/compat/version.js";
4
+ import { pathExists, readTextIfExists } from "../../core/fsutil.js";
5
+ import { boolSetting, readSmapiConfig } from "./config.js";
6
+ import { relaxJson, scanMods } from "./mods.js";
7
+ export const CONTENT_PATCHER_ID = "Pathoschild.ContentPatcher";
8
+ export const SMAPI_MINIMUM_GAME_VERSION = "1.6.14";
9
+ export const CONTENT_PATCHER_MINIMUM_SMAPI = "4.4.0";
10
+ async function firstExisting(files) {
11
+ for (const f of files)
12
+ if (await pathExists(f))
13
+ return f;
14
+ return undefined;
15
+ }
16
+ async function peVersionOf(file) {
17
+ if (!file)
18
+ return undefined;
19
+ try {
20
+ return await readPeFileVersion(file);
21
+ }
22
+ catch {
23
+ return undefined;
24
+ }
25
+ }
26
+ export async function detectComponents(install) {
27
+ const root = install.root;
28
+ const components = [];
29
+ const gameFile = await firstExisting([path.join(root, "Stardew Valley.dll"), path.join(root, "Stardew Valley.exe"), path.join(root, "StardewValley.exe"), path.join(root, "StardewValley")]);
30
+ const gameVersion = install.version ?? (await peVersionOf(gameFile));
31
+ components.push({
32
+ id: "game",
33
+ label: "Stardew Valley",
34
+ kind: "game",
35
+ installed: gameFile !== undefined,
36
+ ...(gameVersion !== undefined ? { version: gameVersion } : {}),
37
+ source: install.version ? "install record" : gameFile ? `${path.basename(gameFile)} PE file version (the assembly version SMAPI reads for its legacy check; whether it equals the 1.6.x patch number is not established)` : "Stardew Valley.dll or the game executable, not found",
38
+ ...(gameFile ? { path: gameFile } : {}),
39
+ });
40
+ const smapiExe = await firstExisting([path.join(root, "StardewModdingAPI.exe"), path.join(root, "StardewModdingAPI")]);
41
+ const smapiDll = path.join(root, "StardewModdingAPI.dll");
42
+ const smapiDllExists = await pathExists(smapiDll);
43
+ const smapiInstalled = smapiExe !== undefined || smapiDllExists;
44
+ const smapiVersion = smapiDllExists ? await peVersionOf(smapiDll) : undefined;
45
+ components.push({
46
+ id: "smapi",
47
+ label: "SMAPI",
48
+ kind: "loader",
49
+ installed: smapiInstalled,
50
+ ...(smapiVersion !== undefined ? { version: smapiVersion } : {}),
51
+ source: "StardewModdingAPI.dll PE file version (the banner line 'SMAPI {v} with Stardew Valley {v}' once it has run)",
52
+ ...(smapiExe ? { path: smapiExe } : smapiDllExists ? { path: smapiDll } : {}),
53
+ });
54
+ const harmonyDll = path.join(root, "smapi-internal", "0Harmony.dll");
55
+ const harmonyVersion = await peVersionOf((await pathExists(harmonyDll)) ? harmonyDll : undefined);
56
+ components.push({ id: "harmony", label: "Harmony (bundled by SMAPI)", kind: "framework", installed: await pathExists(harmonyDll), ...(harmonyVersion !== undefined ? { version: harmonyVersion } : {}), source: "smapi-internal/0Harmony.dll PE file version", path: harmonyDll });
57
+ const modsDir = path.join(root, "Mods");
58
+ const mods = (await pathExists(modsDir)) ? await scanMods(modsDir) : [];
59
+ const cp = mods.find((m) => typeof m.manifest?.UniqueID === "string" && m.manifest.UniqueID.toLowerCase() === CONTENT_PATCHER_ID.toLowerCase());
60
+ const cpVersion = typeof cp?.manifest?.Version === "string" && parseVersion(cp.manifest.Version) ? cp.manifest.Version : undefined;
61
+ components.push({
62
+ id: "contentpatcher",
63
+ label: "Content Patcher",
64
+ kind: "mod",
65
+ installed: cp !== undefined,
66
+ ...(cpVersion !== undefined ? { version: cpVersion } : {}),
67
+ source: "Mods/**/manifest.json with UniqueID Pathoschild.ContentPatcher, its Version field",
68
+ ...(cp ? { path: cp.path } : {}),
69
+ });
70
+ const config = await readSmapiConfig(install);
71
+ const configSource = config.files.length > 0 ? `the merged SMAPI config (${config.files.map((f) => path.relative(root, f)).join(", ")})` : "no SMAPI config file found";
72
+ components.push({ id: "smapi-rewrite-off", label: "SMAPI with RewriteMods disabled", kind: "loader", installed: smapiInstalled && boolSetting(config.settings, "RewriteMods") === false, source: configSource, ...(config.sources.RewriteMods ? { path: config.sources.RewriteMods } : {}) });
73
+ components.push({ id: "smapi-fixharmony-off", label: "SMAPI with FixHarmony disabled", kind: "loader", installed: smapiInstalled && boolSetting(config.settings, "FixHarmony") === false, source: configSource, ...(config.sources.FixHarmony ? { path: config.sources.FixHarmony } : {}) });
74
+ return components;
75
+ }
76
+ export const RULES = [
77
+ {
78
+ id: "sdv-smapi-needs-game-1-6-14",
79
+ game: "stardewvalley",
80
+ component: "smapi",
81
+ requires: [{ component: "game", min: SMAPI_MINIMUM_GAME_VERSION }],
82
+ severity: "warn",
83
+ undeterminedSeverity: "info",
84
+ note: "SMAPI 4.5.x refuses to start on a game older than 1.6.14 (Constants.MinimumGameVersion, a literal in its source); there is no upper bound. Reported as warn rather than block because the game version read here is the assembly version, whose relationship to the 1.6.x patch number has not been checked against a real install.",
85
+ source: "https://github.com/Pathoschild/SMAPI/blob/develop/src/SMAPI/Constants.cs",
86
+ },
87
+ {
88
+ id: "sdv-contentpatcher-needs-smapi-4-4",
89
+ game: "stardewvalley",
90
+ component: "contentpatcher",
91
+ requires: [{ component: "smapi", min: CONTENT_PATCHER_MINIMUM_SMAPI }],
92
+ severity: "warn",
93
+ undeterminedSeverity: "info",
94
+ note: "Content Patcher 2.9.0 and 2.9.1 are released 'for SMAPI 4.4.0 or later', so an older SMAPI is an unsupported combination and every content pack is only as loadable as Content Patcher is. What SMAPI actually enforces is the installed copy's own MinimumApiVersion (4.1.10 in the develop manifest), which is the version its 'it needs SMAPI x or later' skip line quotes, so below 4.4.0 expect misbehaviour rather than necessarily a refusal.",
95
+ source: "https://github.com/Pathoschild/StardewMods/blob/develop/ContentPatcher/docs/release-notes.md ('Released 14 March 2026 for SMAPI 4.4.0 or later') and ContentPatcher/manifest.json's MinimumApiVersion",
96
+ },
97
+ {
98
+ id: "sdv-smapi-rewrites-mods",
99
+ game: "stardewvalley",
100
+ component: "smapi",
101
+ requires: [],
102
+ advisory: true,
103
+ severity: "info",
104
+ note: "SMAPI rewrites older mods for compatibility (RewriteMods, default true) and disables known-broken ones by name from smapi-internal/metadata.json. A mod that fails to load here is more often on that list than genuinely incompatible: read the skipped-mods reason text before assuming a version problem.",
105
+ source: "https://github.com/Pathoschild/SMAPI/blob/develop/src/SMAPI/Framework/ModLoading/Rewriters/; src/SMAPI.Web/wwwroot/SMAPI.metadata.json",
106
+ },
107
+ {
108
+ id: "sdv-game-unpatched-since-1-6-15",
109
+ game: "stardewvalley",
110
+ component: "game",
111
+ requires: [],
112
+ advisory: true,
113
+ severity: "info",
114
+ note: "Stardew Valley has not been patched since 1.6.15 (2024-12-20); 1.7 is announced with no date. Treat a game-version-mismatch hypothesis as unlikely until it ships.",
115
+ source: "https://stardewvalleywiki.com/Version_History",
116
+ },
117
+ {
118
+ id: "sdv-rewritemods-off",
119
+ game: "stardewvalley",
120
+ component: "smapi-rewrite-off",
121
+ requires: [],
122
+ advisory: true,
123
+ severity: "warn",
124
+ note: "RewriteMods is false in SMAPI's config: mods built for an older game are loaded unrewritten, and SMAPI's own config comment warns that they are then likely to crash. A crash in an older mod on this install is not evidence about the mod.",
125
+ source: "https://github.com/Pathoschild/SMAPI/blob/develop/src/SMAPI/SMAPI.config.json",
126
+ },
127
+ {
128
+ id: "sdv-fixharmony-off",
129
+ game: "stardewvalley",
130
+ component: "smapi-fixharmony-off",
131
+ requires: [],
132
+ advisory: true,
133
+ severity: "warn",
134
+ note: "FixHarmony is false in SMAPI's config: SMAPI's own comment says mods which use Harmony are then likely to cause game crashes. Any Harmony-using mod's crash on this install is suspect for that reason first.",
135
+ source: "https://github.com/Pathoschild/SMAPI/blob/develop/src/SMAPI/SMAPI.config.json",
136
+ },
137
+ ];
138
+ export const stardewvalleyCompat = { detectComponents, rules: RULES };
139
+ export async function readModMetadata(install) {
140
+ const text = await readTextIfExists(path.join(install.root, "smapi-internal", "metadata.json"));
141
+ if (text === undefined)
142
+ return undefined;
143
+ let doc;
144
+ try {
145
+ doc = JSON.parse(relaxJson(text));
146
+ }
147
+ catch {
148
+ return undefined;
149
+ }
150
+ const data = doc && typeof doc === "object" ? doc.ModData : undefined;
151
+ if (!data || typeof data !== "object")
152
+ return [];
153
+ const out = [];
154
+ for (const [name, rec] of Object.entries(data)) {
155
+ if (!rec || typeof rec !== "object")
156
+ continue;
157
+ const r = rec;
158
+ const field = (key) => key.split("|").map((p) => p.trim()).pop().toLowerCase();
159
+ const prefix = (key) => key.split("|").map((p) => p.trim()).slice(0, -1).filter((p) => p.toLowerCase() !== "default").join("|");
160
+ let id;
161
+ let formerIds = [];
162
+ const byRange = new Map();
163
+ for (const [key, value] of Object.entries(r)) {
164
+ const f = field(key);
165
+ if (f === "id" && typeof value === "string")
166
+ id = value;
167
+ else if (f === "formerids" && typeof value === "string")
168
+ formerIds = value.split("|").map((s) => s.trim()).filter(Boolean);
169
+ else if ((f === "status" || f === "statusreasonphrase") && typeof value === "string") {
170
+ const p = prefix(key);
171
+ const slot = byRange.get(p) ?? {};
172
+ if (f === "status")
173
+ slot.status = value;
174
+ else
175
+ slot.reason = value;
176
+ byRange.set(p, slot);
177
+ }
178
+ }
179
+ for (const [range, slot] of byRange) {
180
+ if (!slot.status)
181
+ continue;
182
+ out.push({ name, ...(id ? { id } : {}), formerIds, status: slot.status, ...(range ? { range } : {}), ...(slot.reason ? { reason: slot.reason } : {}) });
183
+ }
184
+ }
185
+ return out;
186
+ }
187
+ export function metadataFor(entries, modId) {
188
+ const lower = modId.toLowerCase();
189
+ return entries.filter((e) => e.id?.toLowerCase() === lower || e.formerIds.some((f) => f.toLowerCase() === lower));
190
+ }
@@ -0,0 +1,42 @@
1
+ import * as path from "node:path";
2
+ import { readTextIfExists } from "../../core/fsutil.js";
3
+ import { relaxJson } from "./mods.js";
4
+ export function smapiConfigFiles(root) {
5
+ return [path.join(root, "smapi-internal", "config.json"), path.join(root, "smapi-internal", "config.user.json"), path.join(root, "Mods", "SMAPI-config.json")];
6
+ }
7
+ export async function readSmapiConfig(install) {
8
+ const out = { files: [], unreadable: [], settings: {}, sources: {} };
9
+ for (const file of smapiConfigFiles(install.root)) {
10
+ const text = await readTextIfExists(file);
11
+ if (text === undefined)
12
+ continue;
13
+ out.files.push(file);
14
+ let doc;
15
+ try {
16
+ doc = JSON.parse(relaxJson(text));
17
+ }
18
+ catch {
19
+ out.unreadable.push(file);
20
+ continue;
21
+ }
22
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
23
+ out.unreadable.push(file);
24
+ continue;
25
+ }
26
+ for (const [k, v] of Object.entries(doc)) {
27
+ if (v === null)
28
+ continue;
29
+ out.settings[k] = v;
30
+ out.sources[k] = file;
31
+ }
32
+ }
33
+ return out;
34
+ }
35
+ export function stringList(settings, key) {
36
+ const v = settings[key];
37
+ return Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
38
+ }
39
+ export function boolSetting(settings, key) {
40
+ const v = settings[key];
41
+ return typeof v === "boolean" ? v : undefined;
42
+ }
@@ -0,0 +1,203 @@
1
+ import { compareVersions, parseVersion } from "../../core/compat/version.js";
2
+ import { relaxJson } from "./mods.js";
3
+ export const CURRENT_FORMAT = "2.9.0";
4
+ export const CURRENT_FORMAT_RE = /^2\.9\.[0-9]+$/;
5
+ export const FORMAT_SCHEMA_MESSAGE = "You should always use the latest format version (currently 2.9.0) to enable the latest features, avoid obsolete behavior, and reduce load times.";
6
+ export const FORMAT_ENABLED_OBSOLETE = "1.25.0";
7
+ export const FORMAT_EDITDATA_FROMFILE_REMOVED = "1.18.0";
8
+ export const ACTIONS = ["Load", "EditImage", "EditData", "EditMap", "Include"];
9
+ export const TOP_LEVEL_FIELDS = ["Format", "Changes", "ConfigSchema", "CustomLocations", "DynamicTokens", "AliasTokenNames", "$schema"];
10
+ export const PATCH_FIELDS = [
11
+ "LogName",
12
+ "Action",
13
+ "Target",
14
+ "TargetLocale",
15
+ "Update",
16
+ "FromFile",
17
+ "Enabled",
18
+ "When",
19
+ "Priority",
20
+ "LocalTokens",
21
+ "TextOperations",
22
+ "FromArea",
23
+ "ToArea",
24
+ "PatchMode",
25
+ "Entries",
26
+ "Fields",
27
+ "MoveEntries",
28
+ "TargetField",
29
+ "MapProperties",
30
+ "AddNpcWarps",
31
+ "AddWarps",
32
+ "MapTiles",
33
+ ];
34
+ export const BUILTIN_TOKENS = new Set([
35
+ "Day", "DayEvent", "DayOfWeek", "DaysPlayed", "Season", "Year", "Weather",
36
+ "HasActiveQuest", "HasCaughtFish", "HasCookingRecipe", "HasCraftingRecipe", "HasConversationTopic", "HasFlag", "HasProfession", "HasReadLetter", "HasSeenEvent", "HasVisitedLocation",
37
+ "DailyLuck", "HasDialogueAnswer", "HasWalletItem", "IsMainPlayer", "IsOutdoors", "LocationContext", "LocationName", "LocationOwnerId", "LocationUniqueName", "PlayerGender", "PlayerName", "PreferredPet", "SkillLevel",
38
+ "ChildNames", "ChildGenders", "Hearts", "Relationship", "Roommate", "Spouse",
39
+ "FarmCave", "FarmhouseUpgrade", "FarmMapAsset", "FarmName", "FarmType", "IsCommunityCenterComplete", "IsJojaMartComplete", "HavingChild", "Pregnant", "Time",
40
+ "Count", "Query", "Range", "Round", "Lowercase", "Merge", "PathPart", "Random", "Render", "Uppercase",
41
+ "FirstValidFile", "HasMod", "HasFile", "HasValue", "I18n", "Language", "ModId",
42
+ "AbsoluteFilePath", "FormatAssetName", "InternalAssetKey",
43
+ "FromFile", "Target", "TargetWithoutPath", "TargetPathOnly",
44
+ ].map((t) => t.toLowerCase()));
45
+ const obj = (v) => (v && typeof v === "object" && !Array.isArray(v) ? v : undefined);
46
+ export function pick(raw, name) {
47
+ if (Object.prototype.hasOwnProperty.call(raw, name))
48
+ return { key: name, value: raw[name] };
49
+ const lower = name.toLowerCase();
50
+ for (const k of Object.keys(raw))
51
+ if (k.toLowerCase() === lower)
52
+ return { key: k, value: raw[k] };
53
+ return undefined;
54
+ }
55
+ export function parseContentJson(text) {
56
+ const doc = JSON.parse(relaxJson(text));
57
+ if (!doc || typeof doc !== "object" || Array.isArray(doc))
58
+ throw new Error("content.json is not a JSON object");
59
+ const raw = doc;
60
+ const known = new Map(TOP_LEVEL_FIELDS.map((f) => [f.toLowerCase(), f]));
61
+ const extraFields = [];
62
+ const miscased = [];
63
+ for (const k of Object.keys(raw)) {
64
+ const expected = known.get(k.toLowerCase());
65
+ if (expected === undefined)
66
+ extraFields.push(k);
67
+ else if (expected !== k)
68
+ miscased.push({ key: k, expected });
69
+ }
70
+ return {
71
+ raw,
72
+ format: pick(raw, "Format")?.value,
73
+ changes: pick(raw, "Changes")?.value,
74
+ configSchema: obj(pick(raw, "ConfigSchema")?.value),
75
+ dynamicTokens: pick(raw, "DynamicTokens")?.value,
76
+ aliasTokenNames: obj(pick(raw, "AliasTokenNames")?.value),
77
+ customLocations: pick(raw, "CustomLocations")?.value,
78
+ extraFields,
79
+ miscased,
80
+ };
81
+ }
82
+ export function formatState(v) {
83
+ if (v === undefined || v === null || (typeof v === "string" && v.trim() === ""))
84
+ return "missing";
85
+ if (typeof v !== "string" || !parseVersion(v.trim()))
86
+ return "invalid";
87
+ const s = v.trim();
88
+ if (CURRENT_FORMAT_RE.test(s))
89
+ return "current";
90
+ return compareVersions(s, CURRENT_FORMAT) < 0 ? "older" : "newer";
91
+ }
92
+ export function formatOlderThan(v, than) {
93
+ if (typeof v !== "string" || !parseVersion(v.trim()))
94
+ return false;
95
+ return compareVersions(v.trim(), than) < 0;
96
+ }
97
+ export function patchesOf(cfg) {
98
+ const patches = [];
99
+ const invalid = [];
100
+ if (!Array.isArray(cfg.changes))
101
+ return { patches, invalid };
102
+ const known = new Set(PATCH_FIELDS.map((f) => f.toLowerCase()));
103
+ cfg.changes.forEach((entry, index) => {
104
+ const raw = obj(entry);
105
+ if (!raw) {
106
+ invalid.push(index);
107
+ return;
108
+ }
109
+ const s = (name) => {
110
+ const v = pick(raw, name)?.value;
111
+ return typeof v === "string" ? v : undefined;
112
+ };
113
+ const logName = s("LogName");
114
+ patches.push({
115
+ index,
116
+ raw,
117
+ label: `entry #${index + 1}${logName ? ` (${logName})` : ""}`,
118
+ action: s("Action"),
119
+ target: s("Target"),
120
+ fromFile: s("FromFile"),
121
+ extraFields: Object.keys(raw).filter((k) => !known.has(k.toLowerCase())),
122
+ });
123
+ });
124
+ return { patches, invalid };
125
+ }
126
+ const TOKEN_RE = /\{\{\s*([^{}\s:|]+)/g;
127
+ export function tokenNames(s) {
128
+ const out = [];
129
+ for (const m of s.matchAll(TOKEN_RE))
130
+ out.push(m[1]);
131
+ return out;
132
+ }
133
+ export function hasTokens(s) {
134
+ return s.includes("{{");
135
+ }
136
+ export function splitLexically(s) {
137
+ const parts = [];
138
+ let depth = 0;
139
+ let current = "";
140
+ for (let i = 0; i < s.length; i++) {
141
+ const two = s.slice(i, i + 2);
142
+ if (two === "{{") {
143
+ depth++;
144
+ current += two;
145
+ i++;
146
+ continue;
147
+ }
148
+ if (two === "}}" && depth > 0) {
149
+ depth--;
150
+ current += two;
151
+ i++;
152
+ continue;
153
+ }
154
+ if (s[i] === "," && depth === 0) {
155
+ parts.push(current);
156
+ current = "";
157
+ continue;
158
+ }
159
+ current += s[i];
160
+ }
161
+ parts.push(current);
162
+ const seen = new Set();
163
+ const out = [];
164
+ for (const p of parts) {
165
+ const t = p.trim();
166
+ if (!t || seen.has(t.toLowerCase()))
167
+ continue;
168
+ seen.add(t.toLowerCase());
169
+ out.push(t);
170
+ }
171
+ return out;
172
+ }
173
+ export function conditionTokenName(key) {
174
+ const k = key.trim();
175
+ if (k.startsWith("{{"))
176
+ return undefined;
177
+ const m = /^([^:|\s]+)/.exec(k);
178
+ return m ? m[1] : undefined;
179
+ }
180
+ export function isKnownToken(name, cfg) {
181
+ if (name.includes("/"))
182
+ return true;
183
+ const lower = name.toLowerCase();
184
+ if (BUILTIN_TOKENS.has(lower))
185
+ return true;
186
+ if (cfg.configSchema && Object.keys(cfg.configSchema).some((k) => k.toLowerCase() === lower))
187
+ return true;
188
+ if (Array.isArray(cfg.dynamicTokens) && cfg.dynamicTokens.some((t) => String(obj(t)?.Name ?? "").toLowerCase() === lower))
189
+ return true;
190
+ if (cfg.aliasTokenNames && Object.keys(cfg.aliasTokenNames).some((k) => k.toLowerCase() === lower))
191
+ return true;
192
+ return false;
193
+ }
194
+ export function tokenPathToRegExp(p) {
195
+ const norm = p.replace(/\\/g, "/").trim().replace(/^\.\//, "");
196
+ let out = "^";
197
+ for (const part of norm.split(/(\{\{[^}]*\}\})/)) {
198
+ if (!part)
199
+ continue;
200
+ out += part.startsWith("{{") ? "[^/]*" : part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
201
+ }
202
+ return new RegExp(out + "$", "i");
203
+ }
@@ -0,0 +1,229 @@
1
+ import { promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ import { defaultCacheDir, indexFile, IndexStore } from "../../../core/knowledge/store.js";
4
+ import { resolveDataDir } from "../../../core/userconfig.js";
5
+ import { documentedFields, pageFileName, pageUrl, parseWikiPage, WIKI_PAGES } from "./wiki.js";
6
+ export const WIKI_DATA_ENV = "MODWRIGHT_STARDEWVALLEY_WIKI_DATA";
7
+ export const WIKI_DATA_ID = "stardewvalley.wiki";
8
+ const GAME = "stardewvalley";
9
+ export function createSchema(store) {
10
+ store.exec(`
11
+ CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
12
+ CREATE TABLE IF NOT EXISTS page (
13
+ id INTEGER PRIMARY KEY,
14
+ page TEXT NOT NULL,
15
+ asset TEXT NOT NULL COLLATE NOCASE,
16
+ shape TEXT NOT NULL,
17
+ file TEXT NOT NULL,
18
+ url TEXT NOT NULL,
19
+ last_modified TEXT,
20
+ asset_on_page INTEGER NOT NULL,
21
+ field_count INTEGER NOT NULL
22
+ );
23
+ CREATE TABLE IF NOT EXISTS field (
24
+ id INTEGER PRIMARY KEY,
25
+ page_id INTEGER NOT NULL REFERENCES page(id),
26
+ asset TEXT NOT NULL COLLATE NOCASE,
27
+ name TEXT NOT NULL COLLATE NOCASE,
28
+ section TEXT,
29
+ optional INTEGER NOT NULL,
30
+ position INTEGER,
31
+ purpose TEXT NOT NULL
32
+ );
33
+ CREATE INDEX IF NOT EXISTS idx_field_asset_name ON field(asset, name);
34
+ CREATE INDEX IF NOT EXISTS idx_page_asset ON page(asset);
35
+ `);
36
+ }
37
+ export function isBuilt(store) {
38
+ const row = store.get("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'field'");
39
+ return row !== undefined && store.get("SELECT COUNT(*) AS n FROM field")?.n > 0;
40
+ }
41
+ export async function importWikiPage(store, dataDir, spec) {
42
+ const file = path.join(dataDir, pageFileName(spec.page));
43
+ const html = await fs.readFile(file, "utf8");
44
+ const parsed = parseWikiPage(html);
45
+ const fields = documentedFields(parsed, spec.shape);
46
+ const assetOnPage = parsed.text.replace(/\\/g, "/").toLowerCase().includes(spec.asset.toLowerCase());
47
+ store.run("INSERT INTO page (page, asset, shape, file, url, last_modified, asset_on_page, field_count) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [
48
+ spec.page,
49
+ spec.asset,
50
+ spec.shape,
51
+ pageFileName(spec.page),
52
+ pageUrl(spec.page),
53
+ parsed.lastModified ?? null,
54
+ assetOnPage ? 1 : 0,
55
+ fields.length,
56
+ ]);
57
+ const pageId = store.get("SELECT last_insert_rowid() AS id")?.id;
58
+ if (fields.length > 0) {
59
+ store.insertMany("field", ["page_id", "asset", "name", "section", "optional", "position", "purpose"], fields.map((f) => [pageId, spec.asset, f.name, f.section ?? null, f.optional ? 1 : 0, f.position ?? null, f.purpose]));
60
+ }
61
+ return { page: spec.page, asset: spec.asset, shape: spec.shape, fields: fields.length, assetOnPage, ...(parsed.lastModified !== undefined ? { lastModified: parsed.lastModified } : {}) };
62
+ }
63
+ function summarize(store, sources) {
64
+ const file = indexFile(defaultCacheDir(), GAME);
65
+ const builtAt = store.get("SELECT value FROM meta WHERE key = 'builtAt'")?.value;
66
+ return { game: GAME, file, exists: true, ...(builtAt !== undefined ? { builtAt: String(builtAt) } : {}), tables: store.tableCounts(), sources };
67
+ }
68
+ export async function buildIndex(_install, store, options = {}) {
69
+ const sources = [];
70
+ createSchema(store);
71
+ if (isBuilt(store) && !options.force) {
72
+ sources.push("(schema index already built — pass { force: true } to rebuild)");
73
+ return summarize(store, sources);
74
+ }
75
+ store.exec("DELETE FROM field; DELETE FROM page; DELETE FROM meta;");
76
+ store.run("INSERT OR REPLACE INTO meta (key, value) VALUES ('builtAt', ?)", [new Date().toISOString()]);
77
+ const dataDir = options.dataDir ?? (await resolveDataDir(WIKI_DATA_ID, WIKI_DATA_ENV))?.path;
78
+ if (!dataDir) {
79
+ sources.push(`no wiki pages: fetch them once with check_toolchain action=install data=${WIKI_DATA_ID} (dry-run first, then mode=apply; it remembers the folder), or point at a folder of saved pages (one <page>.html per documented asset) with check_toolchain action=remember data=${WIKI_DATA_ID} dataPath=<folder> or ${WIKI_DATA_ENV}; nothing was indexed.`);
80
+ return summarize(store, sources);
81
+ }
82
+ let specs = [...WIKI_PAGES];
83
+ if (options.only && options.only.length > 0) {
84
+ const wanted = new Set(options.only.map((k) => k.toLowerCase()));
85
+ specs = specs.filter((s) => wanted.has(s.page.toLowerCase()) || wanted.has(s.asset.toLowerCase()));
86
+ }
87
+ const missing = [];
88
+ const errors = [];
89
+ let index = 0;
90
+ for (const spec of specs) {
91
+ options.progress?.({ step: `wiki:${spec.page}`, done: index++, total: specs.length, note: spec.asset });
92
+ const file = path.join(dataDir, pageFileName(spec.page));
93
+ try {
94
+ await fs.access(file);
95
+ }
96
+ catch {
97
+ missing.push(spec.page);
98
+ continue;
99
+ }
100
+ try {
101
+ const s = await importWikiPage(store, dataDir, spec);
102
+ sources.push(`${s.page}.html (${s.asset}, ${s.shape}, ${s.fields} documented field${s.fields === 1 ? "" : "s"}${s.lastModified ? `, ${s.lastModified.replace(/^This page was /, "")}` : ""})${s.assetOnPage ? "" : " — the asset name does not appear on the page; check the curated mapping"}`);
103
+ }
104
+ catch (err) {
105
+ errors.push(`${spec.page}: ${err.message}`);
106
+ }
107
+ }
108
+ store.run("INSERT OR REPLACE INTO meta (key, value) VALUES ('dataDir', ?)", [dataDir]);
109
+ store.run("INSERT OR REPLACE INTO meta (key, value) VALUES ('pages', ?)", [String(specs.length - missing.length)]);
110
+ if (missing.length > 0)
111
+ sources.push(`${missing.length} page(s) not saved under ${dataDir} (${missing.join(", ")}); fetch them with check_toolchain action=install data=${WIKI_DATA_ID}.`);
112
+ if (errors.length > 0) {
113
+ store.run("INSERT OR REPLACE INTO meta (key, value) VALUES ('buildErrors', ?)", [JSON.stringify(errors.slice(0, 200))]);
114
+ sources.push(`${errors.length} page(s) failed to parse — see meta.buildErrors.`);
115
+ }
116
+ sources.push("The wiki states no game version; the pages describe 1.6's data models, each with its own last-edited date. Field lists only: whether a given entry exists needs a value index.");
117
+ return summarize(store, sources);
118
+ }
119
+ export async function info(_install) {
120
+ const file = indexFile(defaultCacheDir(), GAME);
121
+ const result = { game: GAME, file, exists: false };
122
+ let stat;
123
+ try {
124
+ stat = await fs.stat(file);
125
+ }
126
+ catch {
127
+ return result;
128
+ }
129
+ result.exists = true;
130
+ result.sizeBytes = stat.size;
131
+ let store;
132
+ try {
133
+ store = await IndexStore.open(file);
134
+ const builtAt = store.get("SELECT value FROM meta WHERE key = 'builtAt'")?.value;
135
+ if (builtAt !== undefined)
136
+ result.builtAt = String(builtAt);
137
+ result.tables = store.tableCounts();
138
+ const rows = store.all("SELECT page, asset, shape, field_count, last_modified FROM page ORDER BY id");
139
+ if (rows.length > 0)
140
+ result.sources = rows.map((r) => `${r.page}.html (${r.asset}, ${r.shape}, ${r.field_count} fields${r.last_modified ? `, ${String(r.last_modified).replace(/^This page was /, "")}` : ""})`);
141
+ }
142
+ catch {
143
+ }
144
+ finally {
145
+ store?.close();
146
+ }
147
+ return result;
148
+ }
149
+ const DEFAULT_LIMIT = 50;
150
+ function str(v) {
151
+ if (v === undefined)
152
+ return undefined;
153
+ const s = String(v).trim();
154
+ return s.length > 0 ? s : undefined;
155
+ }
156
+ function limitOf(params) {
157
+ const n = Number(params.limit);
158
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : DEFAULT_LIMIT;
159
+ }
160
+ export function normalizeAsset(name) {
161
+ return name.replace(/\\/g, "/").replace(/\.xnb$/i, "").trim();
162
+ }
163
+ export function queryKinds() {
164
+ return [
165
+ { kind: "assets", description: "The data assets the index documents (from the wiki's per-asset pages), each with its shape (model or delimited string), documented field count and the page's last-edited date; `contains` filters by asset name.", params: ["contains", "limit"] },
166
+ { kind: "asset", description: "The documented fields of one asset (e.g. Data/Objects): name, section, whether optional, position for a delimited asset, and the wiki's one-line purpose.", params: ["name", "section", "limit"] },
167
+ { kind: "field", description: "A field by exact name or `contains` within one asset, or across every asset when `asset` is omitted.", params: ["asset", "name", "contains", "limit"] },
168
+ ];
169
+ }
170
+ function like(s) {
171
+ return `%${s.replace(/[%_\\]/g, (c) => `\\${c}`)}%`;
172
+ }
173
+ export async function query(store, kind, params) {
174
+ const limit = limitOf(params);
175
+ const finish = (rows, note) => {
176
+ const truncated = rows.length > limit;
177
+ const out = truncated ? rows.slice(0, limit) : rows;
178
+ return { kind, params, count: out.length, rows: out, ...(truncated ? { truncated: true } : {}), ...(note !== undefined ? { note } : {}) };
179
+ };
180
+ switch (kind) {
181
+ case "assets": {
182
+ const contains = str(params.contains);
183
+ const rows = contains
184
+ ? store.all("SELECT asset, shape, page, url, field_count AS fields, last_modified AS lastModified, asset_on_page AS assetOnPage FROM page WHERE asset LIKE ? ESCAPE '\\' ORDER BY asset LIMIT ?", [like(contains), limit + 1])
185
+ : store.all("SELECT asset, shape, page, url, field_count AS fields, last_modified AS lastModified, asset_on_page AS assetOnPage FROM page ORDER BY asset LIMIT ?", [limit + 1]);
186
+ return finish(rows);
187
+ }
188
+ case "asset": {
189
+ const name = str(params.name);
190
+ if (!name)
191
+ return { kind, params, count: 0, rows: [], note: "asset needs `name` (e.g. Data/Objects)." };
192
+ const section = str(params.section);
193
+ const asset = normalizeAsset(name);
194
+ const page = store.get("SELECT asset, shape, page, url, last_modified AS lastModified FROM page WHERE asset = ?", [asset]);
195
+ if (!page)
196
+ return { kind, params, count: 0, rows: [], note: `${asset} is not one of the assets the wiki pages in this index document (kind "assets" lists them); its field names cannot be checked.` };
197
+ const rows = section
198
+ ? store.all("SELECT f.asset, p.shape, f.name, f.section, f.optional, f.position, f.purpose FROM field f JOIN page p ON p.id = f.page_id WHERE f.asset = ? AND f.section = ? ORDER BY f.id LIMIT ?", [asset, section, limit + 1])
199
+ : store.all("SELECT f.asset, p.shape, f.name, f.section, f.optional, f.position, f.purpose FROM field f JOIN page p ON p.id = f.page_id WHERE f.asset = ? ORDER BY f.id LIMIT ?", [asset, limit + 1]);
200
+ return finish(rows.map((r) => ({ ...r, optional: r.optional === 1 })), `${page.asset}: ${page.shape} shape, documented at ${page.url}${page.lastModified ? ` (${String(page.lastModified).replace(/^This page was /, "")})` : ""}.`);
201
+ }
202
+ case "field": {
203
+ const asset = str(params.asset);
204
+ const name = str(params.name);
205
+ const contains = str(params.contains);
206
+ if (!name && !contains)
207
+ return { kind, params, count: 0, rows: [], note: "field needs `name` or `contains`." };
208
+ const where = [];
209
+ const args = [];
210
+ if (asset) {
211
+ where.push("f.asset = ?");
212
+ args.push(normalizeAsset(asset));
213
+ }
214
+ if (name) {
215
+ where.push("f.name = ?");
216
+ args.push(name);
217
+ }
218
+ else {
219
+ where.push("f.name LIKE ? ESCAPE '\\'");
220
+ args.push(like(contains));
221
+ }
222
+ const rows = store.all(`SELECT f.asset, p.shape, f.name, f.section, f.optional, f.position, f.purpose FROM field f JOIN page p ON p.id = f.page_id WHERE ${where.join(" AND ")} ORDER BY f.asset, f.id LIMIT ?`, [...args, limit + 1]);
223
+ return finish(rows.map((r) => ({ ...r, optional: r.optional === 1 })));
224
+ }
225
+ default:
226
+ return { kind, params, count: 0, rows: [], note: `Unknown kind "${kind}"; see queryKinds().` };
227
+ }
228
+ }
229
+ export const stardewvalleyIndex = { info, build: buildIndex, queryKinds, query };