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,123 @@
1
+ import { existsSync, promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ import initSqlJs from "sql.js";
4
+ import { legacyCacheDirs, userCacheDir } from "../home.js";
5
+ let sqlPromise;
6
+ async function sql() {
7
+ sqlPromise ??= initSqlJs();
8
+ return sqlPromise;
9
+ }
10
+ export class IndexStore {
11
+ file;
12
+ db;
13
+ constructor(file, db) {
14
+ this.file = file;
15
+ this.db = db;
16
+ }
17
+ static async open(file) {
18
+ const SQL = await sql();
19
+ let bytes;
20
+ if (file) {
21
+ try {
22
+ bytes = new Uint8Array(await fs.readFile(file));
23
+ }
24
+ catch {
25
+ bytes = undefined;
26
+ }
27
+ }
28
+ return new IndexStore(file, bytes ? new SQL.Database(bytes) : new SQL.Database());
29
+ }
30
+ run(statement, params = []) {
31
+ this.db.run(statement, params);
32
+ }
33
+ exec(script) {
34
+ this.db.exec(script);
35
+ }
36
+ all(statement, params = []) {
37
+ const stmt = this.db.prepare(statement);
38
+ try {
39
+ stmt.bind(params);
40
+ const rows = [];
41
+ while (stmt.step())
42
+ rows.push(stmt.getAsObject());
43
+ return rows;
44
+ }
45
+ finally {
46
+ stmt.free();
47
+ }
48
+ }
49
+ get(statement, params = []) {
50
+ return this.all(statement, params)[0];
51
+ }
52
+ transaction(fn) {
53
+ this.db.run("BEGIN");
54
+ try {
55
+ const result = fn();
56
+ this.db.run("COMMIT");
57
+ return result;
58
+ }
59
+ catch (error) {
60
+ this.db.run("ROLLBACK");
61
+ throw error;
62
+ }
63
+ }
64
+ insertMany(table, columns, rows) {
65
+ const placeholders = columns.map(() => "?").join(", ");
66
+ const stmt = this.db.prepare(`INSERT INTO ${table} (${columns.join(", ")}) VALUES (${placeholders})`);
67
+ try {
68
+ for (const row of rows) {
69
+ stmt.run(row);
70
+ stmt.reset();
71
+ }
72
+ }
73
+ finally {
74
+ stmt.free();
75
+ }
76
+ return rows.length;
77
+ }
78
+ tableCounts() {
79
+ const counts = {};
80
+ for (const row of this.all("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '%_content' AND name NOT LIKE '%_segments' AND name NOT LIKE '%_segdir' AND name NOT LIKE '%_docsize' AND name NOT LIKE '%_stat'")) {
81
+ const name = String(row.name);
82
+ counts[name] = Number(this.get(`SELECT count(*) AS n FROM "${name}"`)?.n ?? 0);
83
+ }
84
+ return counts;
85
+ }
86
+ async save(file = this.file) {
87
+ if (!file)
88
+ throw new Error("IndexStore.save: no file path given.");
89
+ await fs.mkdir(path.dirname(file), { recursive: true });
90
+ const bytes = this.db.export();
91
+ const tmp = `${file}.tmp-${process.pid}`;
92
+ await fs.writeFile(tmp, bytes);
93
+ await fs.rename(tmp, file);
94
+ return file;
95
+ }
96
+ close() {
97
+ this.db.close();
98
+ }
99
+ }
100
+ export function indexFile(cacheDir, game, name = "vanilla") {
101
+ return path.join(cacheDir, game, `${name}.sqlite`);
102
+ }
103
+ export function defaultCacheDir() {
104
+ return process.env.MODWRIGHT_CACHE_DIR ?? userCacheDir();
105
+ }
106
+ export function findLegacyIndex(game, cacheDir = defaultCacheDir()) {
107
+ if (existsSync(indexFile(cacheDir, game)))
108
+ return undefined;
109
+ for (const dir of legacyCacheDirs(cacheDir)) {
110
+ const file = indexFile(dir, game);
111
+ if (existsSync(file))
112
+ return { dir, file, gameDir: path.dirname(file) };
113
+ }
114
+ return undefined;
115
+ }
116
+ export function resolveIndexFile(game, cacheDir = defaultCacheDir()) {
117
+ const legacy = findLegacyIndex(game, cacheDir);
118
+ return legacy ? { file: legacy.file, legacy } : { file: indexFile(cacheDir, game) };
119
+ }
120
+ export function legacyIndexHint(game, legacy, cacheDir = defaultCacheDir()) {
121
+ return (`The ${game} index is being read from the old cache location (${legacy.gameDir}); ModWright now keeps its cache in ${cacheDir}. ` +
122
+ `Move that folder to ${path.join(cacheDir, game)} when convenient (or set MODWRIGHT_CACHE_DIR=${legacy.dir}).`);
123
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,42 @@
1
+ import * as path from "node:path";
2
+ import { pathExists } from "../fsutil.js";
3
+ import { lookupProbe } from "../testplan/registry.js";
4
+ import { requiresEvidence } from "./status.js";
5
+ export async function checkLedger(projectRoot, claims) {
6
+ const issues = [];
7
+ const seenIds = new Set();
8
+ for (const claim of claims) {
9
+ if (seenIds.has(claim.id)) {
10
+ issues.push({ claimId: claim.id, message: `duplicate claim id "${claim.id}".` });
11
+ }
12
+ seenIds.add(claim.id);
13
+ if (!requiresEvidence(claim.status))
14
+ continue;
15
+ if (!claim.evidence || claim.evidence.length === 0) {
16
+ issues.push({ claimId: claim.id, message: `status "${claim.status}" requires an evidence block, but none is recorded.` });
17
+ continue;
18
+ }
19
+ for (const entry of claim.evidence) {
20
+ const abs = path.isAbsolute(entry.resultPath) ? entry.resultPath : path.join(projectRoot, entry.resultPath);
21
+ if (!(await pathExists(abs))) {
22
+ issues.push({ claimId: claim.id, message: `evidence resultPath "${entry.resultPath}" does not exist on disk.` });
23
+ }
24
+ }
25
+ const usesUnverifiedProbe = claim.evidence.some((e) => !e.manual && lookupProbe(e.probeKind)?.status === "unverified");
26
+ const hasManualCorroboration = claim.evidence.some((e) => e.manual);
27
+ if (usesUnverifiedProbe && !hasManualCorroboration) {
28
+ issues.push({
29
+ claimId: claim.id,
30
+ message: `evidence names an unverified probe kind but carries no manual-row corroboration: ` +
31
+ `an unverified probe can never quietly become the sole proof of a claim.`,
32
+ });
33
+ }
34
+ }
35
+ const ids = new Set(claims.map((c) => c.id));
36
+ for (const claim of claims) {
37
+ if (claim.supersedes && !ids.has(claim.supersedes)) {
38
+ issues.push({ claimId: claim.id, message: `supersedes unknown claim id "${claim.supersedes}".` });
39
+ }
40
+ }
41
+ return { ok: issues.length === 0, issues };
42
+ }
@@ -0,0 +1,80 @@
1
+ import * as path from "node:path";
2
+ import { loadClaims, saveClaims } from "./store.js";
3
+ import { applyClaimTransition } from "./record.js";
4
+ const NONE = { skipped: true, applied: [], refused: [] };
5
+ function toPosix(p) {
6
+ return p.split(path.sep).join("/");
7
+ }
8
+ function claimFileSet(claim, projectRoot) {
9
+ const files = claim.scope?.files ?? [];
10
+ return new Set(files.map((f) => toPosix(path.isAbsolute(f) ? path.relative(projectRoot, f) : f)));
11
+ }
12
+ export async function applyValidateLedgerHook(projectRoot, blockingFindingFiles, ranFiles, tool = "validate", options = {}) {
13
+ const loaded = await loadClaims(projectRoot);
14
+ if (!loaded.exists)
15
+ return NONE;
16
+ const blocked = new Set(blockingFindingFiles.map(toPosix));
17
+ const ran = ranFiles ? new Set(ranFiles.map(toPosix)) : undefined;
18
+ const by = options.by ?? "validate";
19
+ let claims = loaded.claims;
20
+ const applied = [];
21
+ const refused = [];
22
+ for (const claim of loaded.claims) {
23
+ if (claim.status !== "authored")
24
+ continue;
25
+ const files = claimFileSet(claim, projectRoot);
26
+ if (files.size === 0)
27
+ continue;
28
+ const allRan = !ran || [...files].every((f) => ran.has(f));
29
+ if (!allRan)
30
+ continue;
31
+ const anyBlocked = [...files].some((f) => blocked.has(f));
32
+ if (anyBlocked)
33
+ continue;
34
+ const result = applyClaimTransition({ claims, id: claim.id, to: "checker-clean", tool, by, now: options.now });
35
+ if (result.ok) {
36
+ claims = result.claims;
37
+ applied.push({ claimId: claim.id, from: "authored", to: "checker-clean" });
38
+ }
39
+ else {
40
+ refused.push({ claimId: claim.id, reason: result.refusal ?? "refused" });
41
+ }
42
+ }
43
+ if (applied.length === 0)
44
+ return { skipped: false, applied, refused };
45
+ await saveClaims(projectRoot, claims, { mode: options.mode ?? "apply", now: options.now });
46
+ return { skipped: false, applied, refused };
47
+ }
48
+ export async function applyBuildLedgerHook(projectRoot, manifestPath, tool = "build", options = {}) {
49
+ const loaded = await loadClaims(projectRoot);
50
+ if (!loaded.exists)
51
+ return NONE;
52
+ const by = options.by ?? "build";
53
+ let claims = loaded.claims;
54
+ const applied = [];
55
+ const refused = [];
56
+ for (const claim of loaded.claims) {
57
+ if (claim.status !== "checker-clean")
58
+ continue;
59
+ const result = applyClaimTransition({
60
+ claims,
61
+ id: claim.id,
62
+ to: "built",
63
+ tool,
64
+ by,
65
+ now: options.now,
66
+ note: `manifest: ${toPosix(path.relative(projectRoot, manifestPath))}`,
67
+ });
68
+ if (result.ok) {
69
+ claims = result.claims;
70
+ applied.push({ claimId: claim.id, from: "checker-clean", to: "built" });
71
+ }
72
+ else {
73
+ refused.push({ claimId: claim.id, reason: result.refusal ?? "refused" });
74
+ }
75
+ }
76
+ if (applied.length === 0)
77
+ return { skipped: false, applied, refused };
78
+ await saveClaims(projectRoot, claims, { mode: options.mode ?? "apply", now: options.now });
79
+ return { skipped: false, applied, refused };
80
+ }
@@ -0,0 +1,7 @@
1
+ export * from "./types.js";
2
+ export { checkTransition, requiresEvidence } from "./status.js";
3
+ export { claimsFilePath, decisionsFilePath, evidenceDir, loadClaims, loadDecisions, saveClaims, saveDecisions } from "./store.js";
4
+ export { applyClaimTransition, applyDecisionRecord } from "./record.js";
5
+ export { checkLedger } from "./check.js";
6
+ export { applyBuildLedgerHook, applyValidateLedgerHook } from "./hooks.js";
7
+ export { listClaims, showClaim } from "./query.js";
@@ -0,0 +1,17 @@
1
+ export function listClaims(claims, filter = {}) {
2
+ return claims.filter((c) => {
3
+ if (filter.status && c.status !== filter.status)
4
+ return false;
5
+ if (filter.claim && c.id !== filter.claim)
6
+ return false;
7
+ if (filter.file) {
8
+ const files = c.scope?.files ?? [];
9
+ if (!files.some((f) => f === filter.file || f.endsWith(filter.file)))
10
+ return false;
11
+ }
12
+ return true;
13
+ });
14
+ }
15
+ export function showClaim(claims, id) {
16
+ return claims.find((c) => c.id === id);
17
+ }
@@ -0,0 +1,84 @@
1
+ import { checkTransition, requiresEvidence } from "./status.js";
2
+ export function applyClaimTransition(input) {
3
+ const now = input.now ?? (() => new Date());
4
+ const idx = input.claims.findIndex((c) => c.id === input.id);
5
+ const existing = idx === -1 ? undefined : input.claims[idx];
6
+ if (!existing) {
7
+ if (input.to !== "authored" || input.tool !== "ledger") {
8
+ return {
9
+ ok: false,
10
+ claims: input.claims,
11
+ refusal: `claim "${input.id}" does not exist yet; only "ledger record" authoring it with status "authored" can create it.`,
12
+ };
13
+ }
14
+ if (!input.claimText) {
15
+ return { ok: false, claims: input.claims, refusal: `authoring a new claim "${input.id}" requires "claim" (the claim text).` };
16
+ }
17
+ const claim = {
18
+ id: input.id,
19
+ claim: input.claimText,
20
+ scope: input.scope,
21
+ status: "authored",
22
+ history: [{ status: "authored", at: now().toISOString(), by: input.by, note: input.note }],
23
+ evidence: null,
24
+ supersedes: input.supersedes ?? null,
25
+ };
26
+ return { ok: true, claims: [...input.claims, claim], claim };
27
+ }
28
+ const check = checkTransition(existing.status, input.to, input.tool);
29
+ if (!check.ok) {
30
+ return { ok: false, claims: input.claims, refusal: check.rule ?? `transition "${existing.status}" -> "${input.to}" via "${input.tool}" refused.` };
31
+ }
32
+ const evidence = input.evidence && input.evidence.length > 0 ? [...(existing.evidence ?? []), ...input.evidence] : existing.evidence;
33
+ if (requiresEvidence(input.to) && (!evidence || evidence.length === 0)) {
34
+ return {
35
+ ok: false,
36
+ claims: input.claims,
37
+ refusal: `"${input.to}" requires an evidence block; none was given and the claim carries none.`,
38
+ };
39
+ }
40
+ const note = [check.note, input.note].filter((s) => Boolean(s)).join("; ") || undefined;
41
+ const updated = {
42
+ ...existing,
43
+ status: input.to,
44
+ evidence,
45
+ supersedes: input.supersedes !== undefined ? input.supersedes : existing.supersedes,
46
+ history: [...existing.history, { status: input.to, at: now().toISOString(), by: input.by, note }],
47
+ };
48
+ const claims = [...input.claims];
49
+ claims[idx] = updated;
50
+ return { ok: true, claims, claim: updated };
51
+ }
52
+ export function applyDecisionRecord(input) {
53
+ const idx = input.decisions.findIndex((d) => d.id === input.id);
54
+ const existing = idx === -1 ? undefined : input.decisions[idx];
55
+ if (!existing) {
56
+ if (!input.question || !input.appliedDefault || !input.where || input.madeBlind === undefined) {
57
+ return {
58
+ ok: false,
59
+ decisions: input.decisions,
60
+ refusal: `authoring a new decision "${input.id}" requires "question", "appliedDefault", "where" and "madeBlind".`,
61
+ };
62
+ }
63
+ const decision = {
64
+ id: input.id,
65
+ question: input.question,
66
+ appliedDefault: input.appliedDefault,
67
+ where: input.where,
68
+ madeBlind: input.madeBlind,
69
+ resolution: input.resolution ?? { status: "open" },
70
+ };
71
+ return { ok: true, decisions: [...input.decisions, decision], decision };
72
+ }
73
+ const updated = {
74
+ ...existing,
75
+ question: input.question ?? existing.question,
76
+ appliedDefault: input.appliedDefault ?? existing.appliedDefault,
77
+ where: input.where ?? existing.where,
78
+ madeBlind: input.madeBlind ?? existing.madeBlind,
79
+ resolution: input.resolution ?? existing.resolution,
80
+ };
81
+ const decisions = [...input.decisions];
82
+ decisions[idx] = updated;
83
+ return { ok: true, decisions, decision: updated };
84
+ }
@@ -0,0 +1,53 @@
1
+ const STATUS_RANK = {
2
+ authored: 0,
3
+ "checker-clean": 1,
4
+ built: 2,
5
+ verified: 3,
6
+ contradicted: 3,
7
+ };
8
+ const STAGE_NAME = {
9
+ authored: "authored",
10
+ "checker-clean": "checker-clean",
11
+ built: "built",
12
+ verified: "verified",
13
+ contradicted: "contradicted",
14
+ };
15
+ const REQUIRED_TOOL = {
16
+ "checker-clean": "validate",
17
+ built: "build",
18
+ verified: "run_tests",
19
+ contradicted: "run_tests",
20
+ };
21
+ function skippedStagesNote(from, to) {
22
+ const order = ["authored", "checker-clean", "built"];
23
+ const fromRank = STATUS_RANK[from];
24
+ const toRank = STATUS_RANK[to];
25
+ if (toRank - fromRank <= 1)
26
+ return undefined;
27
+ const skipped = order.filter((s) => STATUS_RANK[s] > fromRank && STATUS_RANK[s] < toRank);
28
+ return skipped.length > 0 ? `skipped ${skipped.map((s) => STAGE_NAME[s]).join(", ")}` : undefined;
29
+ }
30
+ export function checkTransition(from, to, tool) {
31
+ if (from === to)
32
+ return { ok: true };
33
+ if (tool === "ledger") {
34
+ return { ok: true, note: skippedStagesNote(from, to) };
35
+ }
36
+ const requiredTool = REQUIRED_TOOL[to];
37
+ if (!requiredTool) {
38
+ return { ok: false, rule: `"${to}" is written only by "ledger record", not "${tool}".` };
39
+ }
40
+ if (tool !== requiredTool) {
41
+ return { ok: false, rule: `"${to}" is written by "${requiredTool}" (the ledger's transition table), not "${tool}".` };
42
+ }
43
+ if (STATUS_RANK[to] < STATUS_RANK[from]) {
44
+ return {
45
+ ok: false,
46
+ rule: `"${from}" → "${to}" is a backward move; "${tool}" only moves a claim forward. Use "ledger record" for a manual correction, with a reason.`,
47
+ };
48
+ }
49
+ return { ok: true, note: skippedStagesNote(from, to) };
50
+ }
51
+ export function requiresEvidence(status) {
52
+ return status === "verified" || status === "contradicted";
53
+ }
@@ -0,0 +1,98 @@
1
+ import { promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ import { parse as parseYaml } from "yaml";
4
+ import { pathExists } from "../fsutil.js";
5
+ import { executePlan, planFile } from "../safety/index.js";
6
+ import { stringifyPreserving } from "../yamledit.js";
7
+ import { parseClaimsFile, parseDecisionsFile } from "./types.js";
8
+ export function claimsFilePath(projectRoot) {
9
+ return path.join(projectRoot, "ledger", "claims.yaml");
10
+ }
11
+ export function decisionsFilePath(projectRoot) {
12
+ return path.join(projectRoot, "ledger", "decisions.yaml");
13
+ }
14
+ export async function loadClaims(projectRoot) {
15
+ const file = claimsFilePath(projectRoot);
16
+ if (!(await pathExists(file))) {
17
+ return { path: file, exists: false, claims: [], problems: [] };
18
+ }
19
+ let text;
20
+ try {
21
+ text = await fs.readFile(file, "utf8");
22
+ }
23
+ catch (err) {
24
+ return { path: file, exists: true, claims: [], problems: [`${file}: cannot read file: ${err.message}`] };
25
+ }
26
+ let raw;
27
+ try {
28
+ raw = parseYaml(text);
29
+ }
30
+ catch (err) {
31
+ return { path: file, exists: true, claims: [], problems: [`${file}: YAML parse error: ${err.message}`] };
32
+ }
33
+ try {
34
+ const parsed = parseClaimsFile(raw);
35
+ return { path: file, exists: true, claims: parsed.claims, problems: [] };
36
+ }
37
+ catch (err) {
38
+ return { path: file, exists: true, claims: [], problems: [err.message] };
39
+ }
40
+ }
41
+ export async function loadDecisions(projectRoot) {
42
+ const file = decisionsFilePath(projectRoot);
43
+ if (!(await pathExists(file))) {
44
+ return { path: file, exists: false, decisions: [], problems: [] };
45
+ }
46
+ let text;
47
+ try {
48
+ text = await fs.readFile(file, "utf8");
49
+ }
50
+ catch (err) {
51
+ return { path: file, exists: true, decisions: [], problems: [`${file}: cannot read file: ${err.message}`] };
52
+ }
53
+ let raw;
54
+ try {
55
+ raw = parseYaml(text);
56
+ }
57
+ catch (err) {
58
+ return { path: file, exists: true, decisions: [], problems: [`${file}: YAML parse error: ${err.message}`] };
59
+ }
60
+ try {
61
+ const parsed = parseDecisionsFile(raw);
62
+ return { path: file, exists: true, decisions: parsed.decisions, problems: [] };
63
+ }
64
+ catch (err) {
65
+ return { path: file, exists: true, decisions: [], problems: [err.message] };
66
+ }
67
+ }
68
+ export async function saveClaims(projectRoot, claims, options = {}) {
69
+ const file = claimsFilePath(projectRoot);
70
+ const content = stringifyPreserving(await fs.readFile(file, "utf8").catch(() => undefined), { claims }, { lineWidth: 0 });
71
+ const plan = {
72
+ purpose: `write ${claims.length} claim(s) to ledger/claims.yaml`,
73
+ operations: [planFile(file, content)],
74
+ backupRoot: path.join(projectRoot, ".modwright", "backups"),
75
+ };
76
+ return executePlan(plan, {
77
+ mode: options.mode ?? "dry-run",
78
+ now: options.now,
79
+ confidence: options.confidence ?? { level: "high", assumptions: [], verifiedBy: [] },
80
+ });
81
+ }
82
+ export async function saveDecisions(projectRoot, decisions, options = {}) {
83
+ const file = decisionsFilePath(projectRoot);
84
+ const content = stringifyPreserving(await fs.readFile(file, "utf8").catch(() => undefined), { decisions }, { lineWidth: 0 });
85
+ const plan = {
86
+ purpose: `write ${decisions.length} decision(s) to ledger/decisions.yaml`,
87
+ operations: [planFile(file, content)],
88
+ backupRoot: path.join(projectRoot, ".modwright", "backups"),
89
+ };
90
+ return executePlan(plan, {
91
+ mode: options.mode ?? "dry-run",
92
+ now: options.now,
93
+ confidence: options.confidence ?? { level: "high", assumptions: [], verifiedBy: [] },
94
+ });
95
+ }
96
+ export function evidenceDir(projectRoot, runId) {
97
+ return path.join(projectRoot, ".modwright", "evidence", runId);
98
+ }
@@ -0,0 +1,108 @@
1
+ import { z } from "zod";
2
+ const iso8601Schema = z.string().refine((s) => !Number.isNaN(Date.parse(s)), {
3
+ message: "must be a parseable ISO 8601 timestamp",
4
+ });
5
+ export const claimStatusSchema = z.enum(["authored", "checker-clean", "built", "verified", "contradicted"]);
6
+ export const claimHistoryEntrySchema = z
7
+ .object({
8
+ status: claimStatusSchema,
9
+ at: iso8601Schema,
10
+ by: z.string().min(1),
11
+ note: z.string().optional(),
12
+ })
13
+ .strict();
14
+ export const claimEvidenceSchema = z
15
+ .object({
16
+ runId: z.string().min(1),
17
+ planId: z.string().min(1),
18
+ rowId: z.string().min(1),
19
+ probeId: z.string().min(1),
20
+ probeKind: z.string().min(1),
21
+ resultPath: z.string().min(1),
22
+ resultSha256: z.string().regex(/^[0-9a-f]{64}$/, "must be a 64-character hex sha256"),
23
+ observedAt: iso8601Schema,
24
+ observed: z.unknown(),
25
+ manual: z.boolean().optional(),
26
+ verifiedWithSetup: z.array(z.string().min(1)).optional(),
27
+ })
28
+ .strict();
29
+ export const claimScopeSchema = z
30
+ .object({
31
+ files: z.array(z.string().min(1)).optional(),
32
+ entries: z.array(z.string().min(1)).optional(),
33
+ })
34
+ .strict();
35
+ export const claimSchema = z
36
+ .object({
37
+ id: z.string().min(1),
38
+ claim: z.string().min(1),
39
+ scope: claimScopeSchema.optional(),
40
+ status: claimStatusSchema,
41
+ history: z.array(claimHistoryEntrySchema).min(1),
42
+ evidence: z.array(claimEvidenceSchema).nullable().default(null),
43
+ supersedes: z.string().min(1).nullable().default(null),
44
+ })
45
+ .strict();
46
+ export const claimsFileSchema = z.object({ claims: z.array(claimSchema) }).strict();
47
+ export const decisionResolutionSchema = z
48
+ .object({
49
+ status: z.enum(["open", "resolved"]),
50
+ at: iso8601Schema.optional(),
51
+ outcome: z.string().optional(),
52
+ source: z.string().optional(),
53
+ })
54
+ .strict();
55
+ export const decisionSchema = z
56
+ .object({
57
+ id: z.string().min(1),
58
+ question: z.string().min(1),
59
+ appliedDefault: z.string().min(1),
60
+ where: z.string().min(1),
61
+ madeBlind: z.boolean(),
62
+ resolution: decisionResolutionSchema,
63
+ })
64
+ .strict();
65
+ export const decisionsFileSchema = z.object({ decisions: z.array(decisionSchema) }).strict();
66
+ function formatPath(segments) {
67
+ if (segments.length === 0)
68
+ return "(root)";
69
+ let out = "";
70
+ for (const segment of segments) {
71
+ out += typeof segment === "number" ? `[${segment}]` : out ? `.${segment}` : segment;
72
+ }
73
+ return out;
74
+ }
75
+ export function parseClaimsFile(raw) {
76
+ const result = claimsFileSchema.safeParse(raw);
77
+ if (result.success)
78
+ return result.data;
79
+ const rawClaims = Array.isArray(raw?.claims) ? (raw.claims) : [];
80
+ const lines = result.error.issues.map((issue) => {
81
+ const [first, second, ...rest] = issue.path;
82
+ if (first === "claims" && typeof second === "number") {
83
+ const rawClaim = rawClaims[second];
84
+ const claimId = typeof rawClaim?.id === "string" ? rawClaim.id : `#${second}`;
85
+ const field = rest.length > 0 ? ` ${formatPath(rest)}` : "";
86
+ return `claims[${second}] (id=${claimId})${field}: ${issue.message}`;
87
+ }
88
+ return `${formatPath(issue.path)}: ${issue.message}`;
89
+ });
90
+ throw new Error(`Invalid ledger/claims.yaml:\n${lines.join("\n")}`);
91
+ }
92
+ export function parseDecisionsFile(raw) {
93
+ const result = decisionsFileSchema.safeParse(raw);
94
+ if (result.success)
95
+ return result.data;
96
+ const rawDecisions = Array.isArray(raw?.decisions) ? (raw.decisions) : [];
97
+ const lines = result.error.issues.map((issue) => {
98
+ const [first, second, ...rest] = issue.path;
99
+ if (first === "decisions" && typeof second === "number") {
100
+ const rawDecision = rawDecisions[second];
101
+ const decisionId = typeof rawDecision?.id === "string" ? rawDecision.id : `#${second}`;
102
+ const field = rest.length > 0 ? ` ${formatPath(rest)}` : "";
103
+ return `decisions[${second}] (id=${decisionId})${field}: ${issue.message}`;
104
+ }
105
+ return `${formatPath(issue.path)}: ${issue.message}`;
106
+ });
107
+ throw new Error(`Invalid ledger/decisions.yaml:\n${lines.join("\n")}`);
108
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./types.js";
2
+ export * from "./registry.js";
3
+ export * from "./scan.js";
4
+ export * from "./triage.js";
@@ -0,0 +1,50 @@
1
+ export class SignatureRegistry {
2
+ byGame = new Map();
3
+ ids = new Set();
4
+ register(signatures) {
5
+ const seenInBatch = new Set();
6
+ for (const sig of signatures) {
7
+ if (!sig.id.startsWith(`${sig.game}.`)) {
8
+ throw new Error(`Signature "${sig.id}" must be prefixed with its game id ("${sig.game}.")`);
9
+ }
10
+ if (this.ids.has(sig.id) || seenInBatch.has(sig.id)) {
11
+ throw new Error(`Duplicate signature id "${sig.id}"`);
12
+ }
13
+ if (sig.pattern.flags.includes("g")) {
14
+ throw new Error(`Signature "${sig.id}" pattern must not use the "g" flag (it carries lastIndex state across scans)`);
15
+ }
16
+ if (sig.extract?.length) {
17
+ const groups = namedGroupsOf(sig.pattern);
18
+ for (const name of sig.extract) {
19
+ if (!groups.has(name)) {
20
+ throw new Error(`Signature "${sig.id}" extracts "${name}" but its pattern has no named group "(?<${name}>...)"`);
21
+ }
22
+ }
23
+ }
24
+ seenInBatch.add(sig.id);
25
+ }
26
+ for (const sig of signatures) {
27
+ this.ids.add(sig.id);
28
+ const list = this.byGame.get(sig.game);
29
+ if (list)
30
+ list.push(sig);
31
+ else
32
+ this.byGame.set(sig.game, [sig]);
33
+ }
34
+ }
35
+ forGame(game) {
36
+ return this.byGame.get(game) ?? [];
37
+ }
38
+ forSource(game, sourceId) {
39
+ return this.forGame(game).filter((sig) => sig.sources.length === 0 || sig.sources.includes(sourceId));
40
+ }
41
+ }
42
+ function namedGroupsOf(pattern) {
43
+ const names = new Set();
44
+ const groupRe = /\(\?<([a-zA-Z_$][a-zA-Z0-9_$]*)>/g;
45
+ let match;
46
+ while ((match = groupRe.exec(pattern.source))) {
47
+ names.add(match[1]);
48
+ }
49
+ return names;
50
+ }