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,191 @@
1
+ import { promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ const DEFAULT_SKIP = ["node_modules", ".git", ".svn", "__macosx"];
4
+ const STAT_CONCURRENCY = 32;
5
+ export async function pathExists(p) {
6
+ try {
7
+ await fs.access(p);
8
+ return true;
9
+ }
10
+ catch {
11
+ return false;
12
+ }
13
+ }
14
+ export async function readLinkTarget(p) {
15
+ const raw = await fs.readlink(p);
16
+ return /^[A-Za-z]:[\\/]$|^[\\/]$/.test(raw) ? raw : raw.replace(/[\\/]+$/, "");
17
+ }
18
+ export async function isDirectory(p) {
19
+ try {
20
+ return (await fs.stat(p)).isDirectory();
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ }
26
+ export async function walk(dir, options = {}) {
27
+ const limit = options.limit ?? 5000;
28
+ const skip = new Set([...DEFAULT_SKIP, ...(options.skipDirs ?? [])]);
29
+ const files = [];
30
+ const links = [];
31
+ const toStat = [];
32
+ let sizeBytes = 0;
33
+ let modifiedAt = new Date(0);
34
+ let truncated = false;
35
+ async function recurse(current, prefix) {
36
+ if (truncated)
37
+ return;
38
+ let entries;
39
+ try {
40
+ entries = await fs.readdir(current, { withFileTypes: true });
41
+ }
42
+ catch {
43
+ return;
44
+ }
45
+ for (const entry of entries) {
46
+ if (truncated)
47
+ return;
48
+ const abs = path.join(current, entry.name);
49
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
50
+ if (entry.isSymbolicLink()) {
51
+ links.push(rel);
52
+ }
53
+ else if (entry.isDirectory()) {
54
+ if (skip.has(entry.name.toLowerCase()))
55
+ continue;
56
+ await recurse(abs, rel);
57
+ }
58
+ else if (entry.isFile()) {
59
+ if (files.length >= limit) {
60
+ truncated = true;
61
+ return;
62
+ }
63
+ files.push(rel);
64
+ if (options.stat !== false)
65
+ toStat.push(abs);
66
+ }
67
+ }
68
+ }
69
+ await recurse(dir, "");
70
+ let next = 0;
71
+ const worker = async () => {
72
+ while (next < toStat.length) {
73
+ const abs = toStat[next++];
74
+ try {
75
+ const stat = await fs.stat(abs);
76
+ sizeBytes += stat.size;
77
+ if (stat.mtime > modifiedAt)
78
+ modifiedAt = stat.mtime;
79
+ }
80
+ catch {
81
+ }
82
+ }
83
+ };
84
+ await Promise.all(Array.from({ length: Math.min(STAT_CONCURRENCY, toStat.length) }, worker));
85
+ return { files, sizeBytes, modifiedAt, truncated, links };
86
+ }
87
+ export async function statFile(p) {
88
+ const stat = await fs.stat(p);
89
+ return {
90
+ files: [path.basename(p)],
91
+ sizeBytes: stat.size,
92
+ modifiedAt: stat.mtime,
93
+ truncated: false,
94
+ };
95
+ }
96
+ export async function readTextIfExists(p) {
97
+ try {
98
+ return await fs.readFile(p, "utf8");
99
+ }
100
+ catch {
101
+ return undefined;
102
+ }
103
+ }
104
+ export async function head(p, maxBytes = 65_536) {
105
+ const handle = await fs.open(p, "r");
106
+ try {
107
+ const { size } = await handle.stat();
108
+ const length = Math.min(Number(size), maxBytes);
109
+ const buffer = Buffer.alloc(length);
110
+ await handle.read(buffer, 0, length, 0);
111
+ return buffer.toString("utf8");
112
+ }
113
+ finally {
114
+ await handle.close();
115
+ }
116
+ }
117
+ export async function tail(p, lines, maxBytes = 512_000) {
118
+ const handle = await fs.open(p, "r");
119
+ try {
120
+ const { size } = await handle.stat();
121
+ const start = Math.max(0, size - maxBytes);
122
+ const length = size - start;
123
+ const buffer = Buffer.alloc(Number(length));
124
+ await handle.read(buffer, 0, Number(length), start);
125
+ const text = buffer.toString("utf8");
126
+ const split = text.split(/\r?\n/);
127
+ return split.slice(Math.max(0, split.length - lines)).join("\n");
128
+ }
129
+ finally {
130
+ await handle.close();
131
+ }
132
+ }
133
+ export async function firstExisting(candidates) {
134
+ for (const candidate of candidates) {
135
+ if (await pathExists(candidate))
136
+ return candidate;
137
+ }
138
+ return undefined;
139
+ }
140
+ export function homePath(...segments) {
141
+ const home = process.env.USERPROFILE ?? process.env.HOME ?? "";
142
+ return path.join(home, ...segments);
143
+ }
144
+ export function localAppData(...segments) {
145
+ const base = process.env.LOCALAPPDATA ?? homePath("AppData", "Local");
146
+ return path.join(base, ...segments);
147
+ }
148
+ export function appData(...segments) {
149
+ const base = process.env.APPDATA ?? homePath("AppData", "Roaming");
150
+ return path.join(base, ...segments);
151
+ }
152
+ export function documents(...segments) {
153
+ return homePath("Documents", ...segments);
154
+ }
155
+ export async function resolveCandidate(candidates) {
156
+ for (const candidate of candidates) {
157
+ const base = path.basename(candidate);
158
+ if (!base.includes("*")) {
159
+ if (await pathExists(candidate))
160
+ return { path: candidate, candidate };
161
+ continue;
162
+ }
163
+ const dir = path.dirname(candidate);
164
+ const regex = new RegExp("^" + base.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*") + "$", "i");
165
+ let entries;
166
+ try {
167
+ entries = await fs.readdir(dir);
168
+ }
169
+ catch {
170
+ continue;
171
+ }
172
+ let best;
173
+ for (const entry of entries) {
174
+ if (!regex.test(entry))
175
+ continue;
176
+ const abs = path.join(dir, entry);
177
+ try {
178
+ const stat = await fs.stat(abs);
179
+ if (!stat.isFile())
180
+ continue;
181
+ if (!best || stat.mtimeMs > best.mtime)
182
+ best = { path: abs, mtime: stat.mtimeMs };
183
+ }
184
+ catch {
185
+ }
186
+ }
187
+ if (best)
188
+ return { path: best.path, candidate };
189
+ }
190
+ return undefined;
191
+ }
@@ -0,0 +1,70 @@
1
+ import { existsSync } from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ function resolve(kind, ctx) {
6
+ const env = ctx.env ?? process.env;
7
+ const platform = ctx.platform ?? process.platform;
8
+ const home = ctx.homedir ?? os.homedir();
9
+ if (env.MODWRIGHT_HOME) {
10
+ const root = path.resolve(env.MODWRIGHT_HOME);
11
+ return kind === "cache" ? path.join(root, "cache") : root;
12
+ }
13
+ if (platform === "win32") {
14
+ const w = path.win32;
15
+ const abs = (v, ...fallback) => (v && w.isAbsolute(v) ? v : w.join(home, ...fallback));
16
+ if (kind === "config")
17
+ return w.join(abs(env.APPDATA, "AppData", "Roaming"), "ModWright");
18
+ const local = w.join(abs(env.LOCALAPPDATA, "AppData", "Local"), "ModWright");
19
+ return kind === "cache" ? w.join(local, "cache") : local;
20
+ }
21
+ const p = path.posix;
22
+ if (platform === "darwin") {
23
+ return kind === "cache" ? p.join(home, "Library", "Caches", "ModWright") : p.join(home, "Library", "Application Support", "ModWright");
24
+ }
25
+ const xdg = (v, ...fallback) => (v && p.isAbsolute(v) ? v : p.join(home, ...fallback));
26
+ if (kind === "config")
27
+ return p.join(xdg(env.XDG_CONFIG_HOME, ".config"), "modwright");
28
+ if (kind === "cache")
29
+ return p.join(xdg(env.XDG_CACHE_HOME, ".cache"), "modwright");
30
+ return p.join(xdg(env.XDG_DATA_HOME, ".local", "share"), "modwright");
31
+ }
32
+ export function userConfigDir(ctx = {}) {
33
+ return resolve("config", ctx);
34
+ }
35
+ export function userCacheDir(ctx = {}) {
36
+ return resolve("cache", ctx);
37
+ }
38
+ export function userDataDir(ctx = {}) {
39
+ return resolve("data", ctx);
40
+ }
41
+ export function packageRoot() {
42
+ let dir = path.dirname(fileURLToPath(import.meta.url));
43
+ for (;;) {
44
+ if (existsSync(path.join(dir, "package.json")))
45
+ return dir;
46
+ const parent = path.dirname(dir);
47
+ if (parent === dir)
48
+ throw new Error("could not find ModWright's package.json above its own module");
49
+ dir = parent;
50
+ }
51
+ }
52
+ export function legacyCacheDirs(current, env = process.env, cwd = process.cwd()) {
53
+ if (env.MODWRIGHT_CACHE_DIR)
54
+ return [];
55
+ const candidates = [path.resolve(cwd, ".modwright-cache")];
56
+ try {
57
+ candidates.push(path.join(packageRoot(), ".modwright-cache"));
58
+ }
59
+ catch {
60
+ }
61
+ const seen = new Set();
62
+ const currentKey = path.resolve(current).toLowerCase();
63
+ return candidates.filter((dir) => {
64
+ const key = path.resolve(dir).toLowerCase();
65
+ if (seen.has(key) || key === currentKey)
66
+ return false;
67
+ seen.add(key);
68
+ return existsSync(dir);
69
+ });
70
+ }
@@ -0,0 +1,229 @@
1
+ import { existsSync, promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { parse as parseYaml } from "yaml";
5
+ import { z } from "zod";
6
+ export const FACT_STATUSES = [
7
+ "verified",
8
+ "unverified",
9
+ "contradicted",
10
+ "community",
11
+ "inferred",
12
+ ];
13
+ export const FACT_ID_PREFIXES = {
14
+ baldursgate3: "bg3.",
15
+ cyberpunk2077: "cp2077.",
16
+ skyrimse: "sse.",
17
+ };
18
+ export function idPrefixFor(game) {
19
+ return FACT_ID_PREFIXES[game] ?? `${game}.`;
20
+ }
21
+ const ID_PATTERN = /^[a-z0-9]+(\.[a-z0-9-]+)+$/;
22
+ const TOPIC_PATTERN = /^[a-z0-9]+(\.[a-z0-9-]+)*$/;
23
+ const KNOWN_FACT_KEYS = [
24
+ "id",
25
+ "claim",
26
+ "status",
27
+ "verified_on",
28
+ "verified_by",
29
+ "source",
30
+ "tags",
31
+ "detail",
32
+ "supersedes",
33
+ "related",
34
+ ];
35
+ const factStatusSchema = z.enum(FACT_STATUSES);
36
+ const optionalString = () => z
37
+ .string()
38
+ .nullish()
39
+ .transform((v) => v ?? undefined);
40
+ const factSchema = z
41
+ .object({
42
+ id: z.string().regex(ID_PATTERN, "id must be dotted lowercase, hyphenated words (e.g. bg3.stats.example)"),
43
+ claim: z.string().refine((v) => v.trim().length >= 15, "claim missing or too short"),
44
+ status: factStatusSchema,
45
+ verified_on: optionalString(),
46
+ verified_by: optionalString(),
47
+ source: z.string().refine((v) => v.trim().length > 0, "source missing"),
48
+ tags: z
49
+ .array(z.string())
50
+ .refine((tags) => tags.every((t) => t === t.toLowerCase()), "tags must be lowercase strings")
51
+ .optional(),
52
+ detail: optionalString(),
53
+ supersedes: optionalString(),
54
+ related: z
55
+ .array(z.union([z.string().min(1), z.object({ id: z.string().min(1), detail: z.string().optional() }).strict()]))
56
+ .transform((entries) => entries.map((r) => (typeof r === "string" ? r : r.id)))
57
+ .optional(),
58
+ })
59
+ .strict();
60
+ function buildFactFileSchema(expectedGame) {
61
+ return z
62
+ .object({
63
+ game: z.string().min(1),
64
+ topic: z.string().regex(TOPIC_PATTERN, "topic must be dotted lowercase"),
65
+ title: z.string().min(1),
66
+ source_docs: z.array(z.string()).optional(),
67
+ facts: z.array(factSchema).min(1, "facts must be a non-empty list"),
68
+ })
69
+ .strict()
70
+ .superRefine((data, ctx) => {
71
+ if (expectedGame !== undefined && data.game !== expectedGame) {
72
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["game"], message: `game must be "${expectedGame}"` });
73
+ }
74
+ const prefix = idPrefixFor(expectedGame ?? data.game);
75
+ const seenAt = new Map();
76
+ data.facts.forEach((fact, i) => {
77
+ if (!fact.id.startsWith(prefix)) {
78
+ ctx.addIssue({
79
+ code: z.ZodIssueCode.custom,
80
+ path: ["facts", i, "id"],
81
+ message: `id must start with "${prefix}" (got ${JSON.stringify(fact.id)})`,
82
+ });
83
+ }
84
+ const earlier = seenAt.get(fact.id);
85
+ if (earlier !== undefined) {
86
+ ctx.addIssue({
87
+ code: z.ZodIssueCode.custom,
88
+ path: ["facts", i, "id"],
89
+ message: `duplicate id ${fact.id} (also facts[${earlier}] in this file)`,
90
+ });
91
+ }
92
+ else {
93
+ seenAt.set(fact.id, i);
94
+ }
95
+ });
96
+ });
97
+ }
98
+ function formatPath(segments) {
99
+ if (segments.length === 0)
100
+ return "(root)";
101
+ let out = "";
102
+ for (const segment of segments) {
103
+ out += typeof segment === "number" ? `[${segment}]` : out ? `.${segment}` : segment;
104
+ }
105
+ return out;
106
+ }
107
+ export async function loadFactFile(filePath, expectedGame) {
108
+ const where = filePath;
109
+ let text;
110
+ try {
111
+ text = await fs.readFile(filePath, "utf8");
112
+ }
113
+ catch (error) {
114
+ return { path: filePath, problems: [`${where}: cannot read file: ${error.message}`] };
115
+ }
116
+ let raw;
117
+ try {
118
+ raw = parseYaml(text);
119
+ }
120
+ catch (error) {
121
+ return { path: filePath, problems: [`${where}: YAML parse error: ${error.message}`] };
122
+ }
123
+ const schema = buildFactFileSchema(expectedGame);
124
+ const result = schema.safeParse(raw);
125
+ if (!result.success) {
126
+ const problems = result.error.issues.map((issue) => `${where} ${formatPath(issue.path)}: ${issue.message}`);
127
+ return { path: filePath, problems };
128
+ }
129
+ return { path: filePath, file: result.data, problems: [] };
130
+ }
131
+ function findPackageRoot(startDir) {
132
+ let dir = startDir;
133
+ for (;;) {
134
+ if (existsSync(path.join(dir, "package.json")))
135
+ return dir;
136
+ const parent = path.dirname(dir);
137
+ if (parent === dir) {
138
+ throw new Error(`loadKnowledge: could not find package.json above ${startDir}`);
139
+ }
140
+ dir = parent;
141
+ }
142
+ }
143
+ export function defaultKnowledgeDir() {
144
+ const here = path.dirname(fileURLToPath(import.meta.url));
145
+ return path.join(findPackageRoot(here), "knowledge");
146
+ }
147
+ export async function loadKnowledge(rootDir, options = {}) {
148
+ const root = rootDir ?? defaultKnowledgeDir();
149
+ const problems = [];
150
+ let gameDirs;
151
+ try {
152
+ gameDirs = (await fs.readdir(root, { withFileTypes: true }))
153
+ .filter((entry) => entry.isDirectory())
154
+ .map((entry) => entry.name)
155
+ .sort();
156
+ }
157
+ catch (error) {
158
+ problems.push(`${root}: cannot read knowledge directory: ${error.message}`);
159
+ return { files: [], facts: [], problems };
160
+ }
161
+ const wanted = options.games ? new Set(options.games) : undefined;
162
+ const loaded = [];
163
+ for (const game of gameDirs) {
164
+ if (wanted && !wanted.has(game))
165
+ continue;
166
+ const dir = path.join(root, game);
167
+ let names;
168
+ try {
169
+ names = (await fs.readdir(dir)).filter((n) => n.endsWith(".yaml") || n.endsWith(".yml")).sort();
170
+ }
171
+ catch (error) {
172
+ problems.push(`${dir}: cannot read: ${error.message}`);
173
+ continue;
174
+ }
175
+ for (const name of names) {
176
+ const filePath = path.join(dir, name);
177
+ const result = await loadFactFile(filePath, game);
178
+ if (result.problems.length > 0 || !result.file) {
179
+ problems.push(...result.problems);
180
+ continue;
181
+ }
182
+ loaded.push({ path: filePath, file: result.file });
183
+ }
184
+ }
185
+ const idOwner = new Map();
186
+ const uniqueFiles = [];
187
+ for (const entry of loaded) {
188
+ let collided = false;
189
+ for (const fact of entry.file.facts) {
190
+ const owner = idOwner.get(fact.id);
191
+ if (owner) {
192
+ problems.push(`${entry.path}: duplicate id ${fact.id} (also in ${owner})`);
193
+ collided = true;
194
+ }
195
+ }
196
+ if (collided)
197
+ continue;
198
+ for (const fact of entry.file.facts)
199
+ idOwner.set(fact.id, entry.path);
200
+ uniqueFiles.push(entry);
201
+ }
202
+ const allIds = new Set(idOwner.keys());
203
+ const finalFiles = [];
204
+ for (const entry of uniqueFiles) {
205
+ for (const fact of entry.file.facts) {
206
+ for (const rel of fact.related ?? []) {
207
+ if (!allIds.has(rel))
208
+ problems.push(`${entry.path}: fact ${fact.id} relates to unknown id "${rel}"`);
209
+ }
210
+ }
211
+ const dangling = entry.file.facts.filter((fact) => fact.supersedes && !allIds.has(fact.supersedes));
212
+ if (dangling.length > 0) {
213
+ for (const fact of dangling) {
214
+ problems.push(`${entry.path}: fact ${fact.id} supersedes unknown id "${fact.supersedes}"`);
215
+ }
216
+ continue;
217
+ }
218
+ finalFiles.push(entry);
219
+ }
220
+ const files = [];
221
+ const facts = [];
222
+ for (const entry of finalFiles) {
223
+ files.push(entry.file);
224
+ for (const fact of entry.file.facts) {
225
+ facts.push({ ...fact, game: entry.file.game, topic: entry.file.topic, topicTitle: entry.file.title });
226
+ }
227
+ }
228
+ return { files, facts, problems };
229
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./types.js";
2
+ export { IndexStore, indexFile, defaultCacheDir, findLegacyIndex, legacyIndexHint, resolveIndexFile } from "./store.js";
3
+ export * from "./facts.js";
4
+ export * from "./search.js";
@@ -0,0 +1,112 @@
1
+ import { compareVersions, isDifferentVersionAxis, parseVersion } from "../compat/version.js";
2
+ const STATUS_RANK = {
3
+ verified: 0,
4
+ community: 1,
5
+ inferred: 2,
6
+ unverified: 3,
7
+ contradicted: 4,
8
+ };
9
+ const WEIGHT = {
10
+ id: 6,
11
+ tag: 5,
12
+ claim: 3,
13
+ detail: 1,
14
+ topic: 2,
15
+ phrase: 4,
16
+ };
17
+ function tokenize(text) {
18
+ const lower = text.toLowerCase().trim();
19
+ if (!lower)
20
+ return [];
21
+ const whole = lower.split(/\s+/).filter(Boolean);
22
+ const split = lower.split(/[^a-z0-9]+/).filter(Boolean);
23
+ return Array.from(new Set([...whole, ...split]));
24
+ }
25
+ function matchesAllTags(fact, tags) {
26
+ const factTags = new Set(fact.tags ?? []);
27
+ return tags.every((t) => factTags.has(t.toLowerCase()));
28
+ }
29
+ function scoreFact(fact, tokens, phrase) {
30
+ if (tokens.length === 0)
31
+ return 0;
32
+ const id = fact.id.toLowerCase();
33
+ const claim = fact.claim.toLowerCase();
34
+ const detail = fact.detail?.toLowerCase() ?? "";
35
+ const tags = (fact.tags ?? []).map((t) => t.toLowerCase());
36
+ const topic = `${fact.topic} ${fact.topicTitle}`.toLowerCase();
37
+ let score = 0;
38
+ for (const token of tokens) {
39
+ if (id.includes(token))
40
+ score += WEIGHT.id;
41
+ for (const tag of tags) {
42
+ if (tag.includes(token) || token.includes(tag))
43
+ score += WEIGHT.tag;
44
+ }
45
+ if (claim.includes(token))
46
+ score += WEIGHT.claim;
47
+ if (detail && detail.includes(token))
48
+ score += WEIGHT.detail;
49
+ if (topic.includes(token))
50
+ score += WEIGHT.topic;
51
+ }
52
+ if (phrase && claim.includes(phrase))
53
+ score += WEIGHT.phrase;
54
+ return score;
55
+ }
56
+ export function computeStale(fact, installedVersion) {
57
+ if (!installedVersion || !fact.verified_on)
58
+ return undefined;
59
+ if (!parseVersion(installedVersion) || !parseVersion(fact.verified_on))
60
+ return undefined;
61
+ if (isDifferentVersionAxis(fact.verified_on, installedVersion))
62
+ return undefined;
63
+ return compareVersions(fact.verified_on, installedVersion) < 0;
64
+ }
65
+ export function searchFacts(entries, query) {
66
+ const limit = query.limit ?? 20;
67
+ const tokens = query.text ? tokenize(query.text) : [];
68
+ const phrase = query.text ? query.text.toLowerCase().trim() : "";
69
+ const hasText = tokens.length > 0;
70
+ const statusSet = query.status ? new Set(query.status) : undefined;
71
+ const tagList = query.tags?.length ? query.tags : undefined;
72
+ const filtered = entries.filter((fact) => {
73
+ if (query.game && fact.game !== query.game)
74
+ return false;
75
+ if (query.topic && fact.topic !== query.topic)
76
+ return false;
77
+ if (tagList && !matchesAllTags(fact, tagList))
78
+ return false;
79
+ if (statusSet && !statusSet.has(fact.status))
80
+ return false;
81
+ return true;
82
+ });
83
+ const scored = filtered
84
+ .map((fact) => ({ fact, score: scoreFact(fact, tokens, phrase) }))
85
+ .filter(({ score }) => !hasText || score > 0);
86
+ scored.sort((a, b) => {
87
+ if (a.score !== b.score)
88
+ return b.score - a.score;
89
+ const statusDiff = STATUS_RANK[a.fact.status] - STATUS_RANK[b.fact.status];
90
+ if (statusDiff !== 0)
91
+ return statusDiff;
92
+ return a.fact.id < b.fact.id ? -1 : a.fact.id > b.fact.id ? 1 : 0;
93
+ });
94
+ return scored.slice(0, limit).map(({ fact, score }) => {
95
+ const stale = computeStale(fact, query.installedVersion);
96
+ const axisMismatch = query.installedVersion !== undefined &&
97
+ fact.verified_on !== undefined &&
98
+ isDifferentVersionAxis(fact.verified_on, query.installedVersion);
99
+ return {
100
+ ...fact,
101
+ score,
102
+ ...(stale === undefined ? {} : { stale }),
103
+ ...(axisMismatch ? { staleUnknownAxis: true } : {}),
104
+ };
105
+ });
106
+ }
107
+ export function factById(entries, id) {
108
+ return entries.find((fact) => fact.id === id);
109
+ }
110
+ export function factsByTopic(entries, topic) {
111
+ return entries.filter((fact) => fact.topic === topic);
112
+ }