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.
- package/LICENSE +35 -0
- package/README.md +212 -0
- package/assets/claude/agents/mw-explore.md +46 -0
- package/assets/claude/agents/mw-fixture.md +33 -0
- package/assets/claude/agents/mw-rule-author.md +51 -0
- package/assets/claude/agents/mw-scaffold-author.md +48 -0
- package/assets/claude/skills/mw-author.md +59 -0
- package/assets/claude/skills/mw-new-item.md +51 -0
- package/assets/claude/skills/mw-new-spell.md +50 -0
- package/assets/claude/skills/mw-scaffold.md +59 -0
- package/assets/claude/skills/mw-ship.md +57 -0
- package/assets/claude/skills/mw-validate.md +51 -0
- package/assets/claude/skills/mw-verify.md +59 -0
- package/assets/nivalisnights/extract_resources.py +177 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Config.json +5 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/BootstrapClient.lua +55 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/BootstrapServer.lua +81 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Log.lua +96 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Probes.lua +1038 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/ProbesClient.lua +74 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Protocol.lua +985 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Watch.lua +175 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/meta.lsx +45 -0
- package/bridges/bg3-se/README.md +99 -0
- package/bridges/bg3-se/bridge.json +68 -0
- package/bridges/cp2077-cet/ModWrightBridge/init.lua +375 -0
- package/bridges/cp2077-cet/ModWrightBridge/probes.lua +1104 -0
- package/bridges/cp2077-cet/ModWrightBridge/protocol.lua +1306 -0
- package/bridges/cp2077-cet/README.md +165 -0
- package/bridges/cp2077-cet/bridge.json +53 -0
- package/dist/core/bridge/arm.js +175 -0
- package/dist/core/bridge/cet-console.js +26 -0
- package/dist/core/bridge/cet-log.js +362 -0
- package/dist/core/bridge/index.js +11 -0
- package/dist/core/bridge/locate.js +35 -0
- package/dist/core/bridge/paths.js +34 -0
- package/dist/core/bridge/pending.js +89 -0
- package/dist/core/bridge/poll.js +155 -0
- package/dist/core/bridge/stage.js +100 -0
- package/dist/core/bridge/status.js +97 -0
- package/dist/core/bridge/types.js +145 -0
- package/dist/core/bridge/validate.js +107 -0
- package/dist/core/build/index.js +6 -0
- package/dist/core/build/manifest.js +47 -0
- package/dist/core/build/paths.js +32 -0
- package/dist/core/build/run.js +342 -0
- package/dist/core/build/stage.js +265 -0
- package/dist/core/build/steps/bg3.js +129 -0
- package/dist/core/build/steps/core.js +195 -0
- package/dist/core/build/steps/cyberpunk.js +95 -0
- package/dist/core/build/steps/listing.js +35 -0
- package/dist/core/build/types.js +13 -0
- package/dist/core/build/zip.js +148 -0
- package/dist/core/compat/check.js +113 -0
- package/dist/core/compat/pe.js +220 -0
- package/dist/core/compat/types.js +1 -0
- package/dist/core/compat/version.js +70 -0
- package/dist/core/deploy/guards.js +292 -0
- package/dist/core/deploy/index.js +5 -0
- package/dist/core/deploy/plan.js +696 -0
- package/dist/core/deploy/reload-apply.js +361 -0
- package/dist/core/deploy/reload.js +358 -0
- package/dist/core/deploy/sources.js +169 -0
- package/dist/core/deploy/types.js +1 -0
- package/dist/core/epic.js +54 -0
- package/dist/core/fsutil.js +191 -0
- package/dist/core/home.js +70 -0
- package/dist/core/knowledge/facts.js +229 -0
- package/dist/core/knowledge/index.js +4 -0
- package/dist/core/knowledge/search.js +112 -0
- package/dist/core/knowledge/store.js +123 -0
- package/dist/core/knowledge/types.js +1 -0
- package/dist/core/ledger/check.js +42 -0
- package/dist/core/ledger/hooks.js +80 -0
- package/dist/core/ledger/index.js +7 -0
- package/dist/core/ledger/query.js +17 -0
- package/dist/core/ledger/record.js +84 -0
- package/dist/core/ledger/status.js +53 -0
- package/dist/core/ledger/store.js +98 -0
- package/dist/core/ledger/types.js +108 -0
- package/dist/core/logs/index.js +4 -0
- package/dist/core/logs/registry.js +50 -0
- package/dist/core/logs/scan.js +195 -0
- package/dist/core/logs/triage.js +160 -0
- package/dist/core/logs/types.js +1 -0
- package/dist/core/lsx.js +10 -0
- package/dist/core/lz4.js +66 -0
- package/dist/core/mods.js +229 -0
- package/dist/core/process.js +49 -0
- package/dist/core/project/build-steps.js +83 -0
- package/dist/core/project/describe.js +74 -0
- package/dist/core/project/index.js +5 -0
- package/dist/core/project/load.js +146 -0
- package/dist/core/project/schema.js +264 -0
- package/dist/core/project/types.js +3 -0
- package/dist/core/prompts.js +72 -0
- package/dist/core/registry.js +35 -0
- package/dist/core/safety/backup.js +206 -0
- package/dist/core/safety/execute.js +188 -0
- package/dist/core/safety/index.js +5 -0
- package/dist/core/safety/plan.js +122 -0
- package/dist/core/safety/restore.js +172 -0
- package/dist/core/safety/types.js +1 -0
- package/dist/core/scaffold/bootstrap.js +7 -0
- package/dist/core/scaffold/catalog.js +28 -0
- package/dist/core/scaffold/claude-assets.js +271 -0
- package/dist/core/scaffold/collisions.js +64 -0
- package/dist/core/scaffold/emit.js +356 -0
- package/dist/core/scaffold/engine.js +680 -0
- package/dist/core/scaffold/ids.js +78 -0
- package/dist/core/scaffold/index.js +10 -0
- package/dist/core/scaffold/jsonpath.js +121 -0
- package/dist/core/scaffold/load.js +81 -0
- package/dist/core/scaffold/render.js +85 -0
- package/dist/core/scaffold/sequence.js +396 -0
- package/dist/core/scaffold/types.js +309 -0
- package/dist/core/steam.js +63 -0
- package/dist/core/surfaceutil.js +36 -0
- package/dist/core/testplan/check.js +67 -0
- package/dist/core/testplan/index.js +5 -0
- package/dist/core/testplan/load.js +100 -0
- package/dist/core/testplan/registry.js +317 -0
- package/dist/core/testplan/run.js +389 -0
- package/dist/core/testplan/types.js +82 -0
- package/dist/core/text/converters/bg3.js +50 -0
- package/dist/core/text/converters/cyberpunk.js +36 -0
- package/dist/core/text/converters/index.js +261 -0
- package/dist/core/text/diff/cr2w.js +180 -0
- package/dist/core/text/diff/index.js +16 -0
- package/dist/core/text/diff/loca.js +43 -0
- package/dist/core/text/diff/lsx.js +126 -0
- package/dist/core/text/handles.js +86 -0
- package/dist/core/text/index.js +6 -0
- package/dist/core/text/json.js +46 -0
- package/dist/core/text/templates/cache.js +71 -0
- package/dist/core/text/templates/index.js +3 -0
- package/dist/core/text/templates/library.js +245 -0
- package/dist/core/text/templates/types.js +51 -0
- package/dist/core/text/toolchain.js +16 -0
- package/dist/core/text/types.js +1 -0
- package/dist/core/text/validate.js +149 -0
- package/dist/core/text/xml.js +58 -0
- package/dist/core/toolchain/datafetch.js +151 -0
- package/dist/core/toolchain/env.js +63 -0
- package/dist/core/toolchain/features.js +170 -0
- package/dist/core/toolchain/index.js +8 -0
- package/dist/core/toolchain/install.js +191 -0
- package/dist/core/toolchain/locate.js +275 -0
- package/dist/core/toolchain/remember.js +84 -0
- package/dist/core/toolchain/run.js +318 -0
- package/dist/core/toolchain/specs.js +351 -0
- package/dist/core/toolchain/types.js +1 -0
- package/dist/core/types.js +1 -0
- package/dist/core/userconfig.js +66 -0
- package/dist/core/validate/context.js +139 -0
- package/dist/core/validate/index.js +5 -0
- package/dist/core/validate/registry.js +58 -0
- package/dist/core/validate/run.js +131 -0
- package/dist/core/validate/suppressions.js +91 -0
- package/dist/core/validate/types.js +2 -0
- package/dist/core/yamledit.js +51 -0
- package/dist/index.js +389 -0
- package/dist/server.js +1914 -0
- package/dist/surfaces/baldursgate3/compat.js +164 -0
- package/dist/surfaces/baldursgate3/identity.js +110 -0
- package/dist/surfaces/baldursgate3/index/build.js +331 -0
- package/dist/surfaces/baldursgate3/index/index.js +17 -0
- package/dist/surfaces/baldursgate3/index/info.js +40 -0
- package/dist/surfaces/baldursgate3/index/parsers/loca.js +41 -0
- package/dist/surfaces/baldursgate3/index/parsers/lsx.js +137 -0
- package/dist/surfaces/baldursgate3/index/parsers/stats.js +45 -0
- package/dist/surfaces/baldursgate3/index/parsers/treasuretable.js +57 -0
- package/dist/surfaces/baldursgate3/index/parsers/xmlutil.js +142 -0
- package/dist/surfaces/baldursgate3/index/queries.js +327 -0
- package/dist/surfaces/baldursgate3/index/schema.js +62 -0
- package/dist/surfaces/baldursgate3/index.js +3 -0
- package/dist/surfaces/baldursgate3/logs.js +278 -0
- package/dist/surfaces/baldursgate3/modsettings.js +115 -0
- package/dist/surfaces/baldursgate3/pak.js +153 -0
- package/dist/surfaces/baldursgate3/settings.js +49 -0
- package/dist/surfaces/baldursgate3/surface.js +232 -0
- package/dist/surfaces/baldursgate3/validators/bridge.js +7 -0
- package/dist/surfaces/baldursgate3/validators/files.js +476 -0
- package/dist/surfaces/baldursgate3/validators/items.js +1001 -0
- package/dist/surfaces/baldursgate3/validators/progression.js +734 -0
- package/dist/surfaces/baldursgate3/validators/stats.js +1076 -0
- package/dist/surfaces/baldursgate3/validators/story.js +790 -0
- package/dist/surfaces/baldursgate3/validators/templates.js +7 -0
- package/dist/surfaces/cyberpunk2077/compat.js +223 -0
- package/dist/surfaces/cyberpunk2077/index/build.js +231 -0
- package/dist/surfaces/cyberpunk2077/index/index.js +17 -0
- package/dist/surfaces/cyberpunk2077/index/info.js +51 -0
- package/dist/surfaces/cyberpunk2077/index/model.js +52 -0
- package/dist/surfaces/cyberpunk2077/index/parsers/tweak.js +483 -0
- package/dist/surfaces/cyberpunk2077/index/parsers/tweakxl-yaml.js +183 -0
- package/dist/surfaces/cyberpunk2077/index/queries.js +504 -0
- package/dist/surfaces/cyberpunk2077/index/resource.js +135 -0
- package/dist/surfaces/cyberpunk2077/index/schema.js +90 -0
- package/dist/surfaces/cyberpunk2077/index.js +3 -0
- package/dist/surfaces/cyberpunk2077/logs.js +742 -0
- package/dist/surfaces/cyberpunk2077/paths.js +12 -0
- package/dist/surfaces/cyberpunk2077/surface.js +218 -0
- package/dist/surfaces/cyberpunk2077/validators/bridge.js +7 -0
- package/dist/surfaces/cyberpunk2077/validators/cr2w.js +349 -0
- package/dist/surfaces/cyberpunk2077/validators/garments.js +1181 -0
- package/dist/surfaces/cyberpunk2077/validators/mesh.js +338 -0
- package/dist/surfaces/cyberpunk2077/validators/modsettings.js +120 -0
- package/dist/surfaces/cyberpunk2077/validators/packaging.js +463 -0
- package/dist/surfaces/cyberpunk2077/validators/red4ext.js +318 -0
- package/dist/surfaces/cyberpunk2077/validators/redscript.js +193 -0
- package/dist/surfaces/cyberpunk2077/validators/templates.js +7 -0
- package/dist/surfaces/cyberpunk2077/validators/tweaks.js +768 -0
- package/dist/surfaces/cyberpunk2077/validators/vehicles.js +531 -0
- package/dist/surfaces/cyberpunk2077/validators/xl.js +308 -0
- package/dist/surfaces/eldenring/binders.js +111 -0
- package/dist/surfaces/eldenring/compat.js +166 -0
- package/dist/surfaces/eldenring/dcx.js +49 -0
- package/dist/surfaces/eldenring/index.js +13 -0
- package/dist/surfaces/eldenring/loaders.js +132 -0
- package/dist/surfaces/eldenring/logs.js +67 -0
- package/dist/surfaces/eldenring/paramdef.js +256 -0
- package/dist/surfaces/eldenring/params.js +129 -0
- package/dist/surfaces/eldenring/surface.js +121 -0
- package/dist/surfaces/eldenring/tools.js +52 -0
- package/dist/surfaces/eldenring/validators/binders.js +193 -0
- package/dist/surfaces/eldenring/validators/packages.js +159 -0
- package/dist/surfaces/eldenring/validators/params.js +334 -0
- package/dist/surfaces/eldenring/validators/profiles.js +287 -0
- package/dist/surfaces/index.js +34 -0
- package/dist/surfaces/nivalisnights/compat.js +229 -0
- package/dist/surfaces/nivalisnights/index/build.js +310 -0
- package/dist/surfaces/nivalisnights/index/index.js +5 -0
- package/dist/surfaces/nivalisnights/index/queries.js +231 -0
- package/dist/surfaces/nivalisnights/index.js +4 -0
- package/dist/surfaces/nivalisnights/logs.js +473 -0
- package/dist/surfaces/nivalisnights/surface.js +195 -0
- package/dist/surfaces/skyrimse/archives.js +229 -0
- package/dist/surfaces/skyrimse/compat.js +163 -0
- package/dist/surfaces/skyrimse/index.js +12 -0
- package/dist/surfaces/skyrimse/loadorder.js +174 -0
- package/dist/surfaces/skyrimse/logs.js +241 -0
- package/dist/surfaces/skyrimse/plugins.js +149 -0
- package/dist/surfaces/skyrimse/records.js +94 -0
- package/dist/surfaces/skyrimse/surface.js +240 -0
- package/dist/surfaces/skyrimse/validators/archive-records.js +165 -0
- package/dist/surfaces/skyrimse/validators/archives.js +110 -0
- package/dist/surfaces/skyrimse/validators/plugins.js +374 -0
- package/dist/surfaces/skyrimse/validators/records.js +117 -0
- package/dist/surfaces/stardewvalley/compat.js +190 -0
- package/dist/surfaces/stardewvalley/config.js +42 -0
- package/dist/surfaces/stardewvalley/contentpatcher.js +203 -0
- package/dist/surfaces/stardewvalley/index/index.js +229 -0
- package/dist/surfaces/stardewvalley/index/wiki.js +190 -0
- package/dist/surfaces/stardewvalley/index.js +12 -0
- package/dist/surfaces/stardewvalley/logs.js +307 -0
- package/dist/surfaces/stardewvalley/mods.js +182 -0
- package/dist/surfaces/stardewvalley/surface.js +244 -0
- package/dist/surfaces/stardewvalley/validators/content.js +321 -0
- package/dist/surfaces/stardewvalley/validators/fields.js +147 -0
- package/dist/surfaces/stardewvalley/validators/loadorder.js +69 -0
- package/dist/surfaces/stardewvalley/validators/manifest.js +221 -0
- package/dist/surfaces/subnautica2/compat.js +138 -0
- package/dist/surfaces/subnautica2/index.js +5 -0
- package/dist/surfaces/subnautica2/logs.js +129 -0
- package/dist/surfaces/subnautica2/paks.js +173 -0
- package/dist/surfaces/subnautica2/surface.js +151 -0
- package/dist/surfaces/subnautica2/validators/packaging.js +298 -0
- package/dist/surfaces/valheim/compat.js +171 -0
- package/dist/surfaces/valheim/index/index.js +301 -0
- package/dist/surfaces/valheim/index/tables.js +103 -0
- package/dist/surfaces/valheim/index.js +11 -0
- package/dist/surfaces/valheim/logs.js +197 -0
- package/dist/surfaces/valheim/packaging.js +131 -0
- package/dist/surfaces/valheim/plugins.js +270 -0
- package/dist/surfaces/valheim/references.js +121 -0
- package/dist/surfaces/valheim/surface.js +223 -0
- package/dist/surfaces/valheim/validators/packaging.js +218 -0
- package/dist/surfaces/valheim/validators/plugins.js +287 -0
- package/dist/surfaces/valheim/validators/references.js +169 -0
- package/knowledge/baldursgate3/animations.gr2.yaml +166 -0
- package/knowledge/baldursgate3/audio.voice.yaml +182 -0
- package/knowledge/baldursgate3/audio.wwise.yaml +104 -0
- package/knowledge/baldursgate3/charactercreation.heads-hair.yaml +193 -0
- package/knowledge/baldursgate3/communitylibrary.shared-content.yaml +129 -0
- package/knowledge/baldursgate3/compatibilityframework.api.yaml +176 -0
- package/knowledge/baldursgate3/crash.causes.yaml +90 -0
- package/knowledge/baldursgate3/dialogue.dialogbank.yaml +120 -0
- package/knowledge/baldursgate3/dialogue.files.yaml +236 -0
- package/knowledge/baldursgate3/dialogue.osiris-goals.yaml +386 -0
- package/knowledge/baldursgate3/dialogue.yaml +122 -0
- package/knowledge/baldursgate3/feats.classes.yaml +164 -0
- package/knowledge/baldursgate3/items.cloth-physics.yaml +167 -0
- package/knowledge/baldursgate3/items.roottemplates.yaml +320 -0
- package/knowledge/baldursgate3/items.visualbank.yaml +628 -0
- package/knowledge/baldursgate3/journal.quests.yaml +214 -0
- package/knowledge/baldursgate3/loadorder.modsettings.yaml +128 -0
- package/knowledge/baldursgate3/localization.handles.yaml +92 -0
- package/knowledge/baldursgate3/misc.encoding-and-practice.yaml +111 -0
- package/knowledge/baldursgate3/origins.companions.yaml +144 -0
- package/knowledge/baldursgate3/osiris.signatures.yaml +211 -0
- package/knowledge/baldursgate3/packaging.meta.yaml +155 -0
- package/knowledge/baldursgate3/packaging.paks.yaml +79 -0
- package/knowledge/baldursgate3/process.launch.yaml +34 -0
- package/knowledge/baldursgate3/progressions.yaml +92 -0
- package/knowledge/baldursgate3/publishing.modio-nexus.yaml +170 -0
- package/knowledge/baldursgate3/publishing.toolkit-modio.yaml +135 -0
- package/knowledge/baldursgate3/races.progression.yaml +228 -0
- package/knowledge/baldursgate3/scriptextender.console.yaml +257 -0
- package/knowledge/baldursgate3/scriptextender.yaml +252 -0
- package/knowledge/baldursgate3/stats.boosts.yaml +293 -0
- package/knowledge/baldursgate3/stats.equipment-kits.yaml +69 -0
- package/knowledge/baldursgate3/stats.interrupts.yaml +45 -0
- package/knowledge/baldursgate3/stats.itemcombos.yaml +65 -0
- package/knowledge/baldursgate3/stats.spells.yaml +293 -0
- package/knowledge/baldursgate3/stats.weapons.yaml +210 -0
- package/knowledge/baldursgate3/toolchain.divine.yaml +179 -0
- package/knowledge/baldursgate3/toolchain.lslib-formats.yaml +242 -0
- package/knowledge/baldursgate3/toolkit.levels.yaml +177 -0
- package/knowledge/baldursgate3/ui.icons-atlas.yaml +78 -0
- package/knowledge/baldursgate3/ui.icons.yaml +412 -0
- package/knowledge/baldursgate3/ui.mcm.yaml +161 -0
- package/knowledge/baldursgate3/vfx.materials.yaml +174 -0
- package/knowledge/baldursgate3/visuals.characterfix.yaml +73 -0
- package/knowledge/cyberpunk2077/animations.anims.yaml +118 -0
- package/knowledge/cyberpunk2077/anims.archivexl.yaml +163 -0
- package/knowledge/cyberpunk2077/anims.authoring.yaml +579 -0
- package/knowledge/cyberpunk2077/anims.encoder.yaml +156 -0
- package/knowledge/cyberpunk2077/anims.graph.yaml +629 -0
- package/knowledge/cyberpunk2077/anims.method.yaml +360 -0
- package/knowledge/cyberpunk2077/anims.pipeline.yaml +352 -0
- package/knowledge/cyberpunk2077/anims.roundtrip.yaml +222 -0
- package/knowledge/cyberpunk2077/anims.sets.yaml +176 -0
- package/knowledge/cyberpunk2077/archivexl.bodytypes.yaml +149 -0
- package/knowledge/cyberpunk2077/archivexl.dynamic-appearances.yaml +218 -0
- package/knowledge/cyberpunk2077/archivexl.manifest.yaml +214 -0
- package/knowledge/cyberpunk2077/audio.events.yaml +168 -0
- package/knowledge/cyberpunk2077/audio.sounds.yaml +164 -0
- package/knowledge/cyberpunk2077/audio.voicesets.yaml +146 -0
- package/knowledge/cyberpunk2077/cet.sandbox.yaml +219 -0
- package/knowledge/cyberpunk2077/clothing.garments.yaml +283 -0
- package/knowledge/cyberpunk2077/clothing.refits.yaml +215 -0
- package/knowledge/cyberpunk2077/codeware.overview.yaml +111 -0
- package/knowledge/cyberpunk2077/codeware.systems.yaml +192 -0
- package/knowledge/cyberpunk2077/drones.combat.yaml +416 -0
- package/knowledge/cyberpunk2077/drones.control.yaml +552 -0
- package/knowledge/cyberpunk2077/entities.appearance.yaml +166 -0
- package/knowledge/cyberpunk2077/equipmentex.slots.yaml +151 -0
- package/knowledge/cyberpunk2077/fx.effects.yaml +246 -0
- package/knowledge/cyberpunk2077/fx.lights.yaml +171 -0
- package/knowledge/cyberpunk2077/hair.modding.yaml +145 -0
- package/knowledge/cyberpunk2077/items.wiring.yaml +246 -0
- package/knowledge/cyberpunk2077/loadorder.archives.yaml +71 -0
- package/knowledge/cyberpunk2077/mesh.blender.yaml +209 -0
- package/knowledge/cyberpunk2077/nativedb.dump.yaml +98 -0
- package/knowledge/cyberpunk2077/npc.appearance.yaml +275 -0
- package/knowledge/cyberpunk2077/npc.behaviour.yaml +128 -0
- package/knowledge/cyberpunk2077/npc.locomotion.yaml +490 -0
- package/knowledge/cyberpunk2077/packaging.deploy.yaml +175 -0
- package/knowledge/cyberpunk2077/packaging.variants.yaml +176 -0
- package/knowledge/cyberpunk2077/player.control.yaml +918 -0
- package/knowledge/cyberpunk2077/process.assetbuild.yaml +154 -0
- package/knowledge/cyberpunk2077/process.lessons.yaml +276 -0
- package/knowledge/cyberpunk2077/quests.minor.yaml +46 -0
- package/knowledge/cyberpunk2077/quests.scenes.yaml +282 -0
- package/knowledge/cyberpunk2077/quickhacks.tiers.yaml +41 -0
- package/knowledge/cyberpunk2077/red4ext.api.yaml +645 -0
- package/knowledge/cyberpunk2077/red4ext.plugins.yaml +124 -0
- package/knowledge/cyberpunk2077/redhottools.hotreload.yaml +245 -0
- package/knowledge/cyberpunk2077/redscript.compile-log.yaml +76 -0
- package/knowledge/cyberpunk2077/redscript.modules.yaml +118 -0
- package/knowledge/cyberpunk2077/toolchain.wolvenkit.yaml +163 -0
- package/knowledge/cyberpunk2077/tweakdb.loot-recipes.yaml +184 -0
- package/knowledge/cyberpunk2077/tweakdb.melee.yaml +352 -0
- package/knowledge/cyberpunk2077/tweakdb.weapons.yaml +325 -0
- package/knowledge/cyberpunk2077/tweakxl.hotreload.yaml +151 -0
- package/knowledge/cyberpunk2077/tweakxl.yaml-syntax.yaml +208 -0
- package/knowledge/cyberpunk2077/ui.icons.yaml +259 -0
- package/knowledge/cyberpunk2077/ui.inkatlas.yaml +81 -0
- package/knowledge/cyberpunk2077/ui.modsettings.yaml +204 -0
- package/knowledge/cyberpunk2077/vehicles.control.yaml +475 -0
- package/knowledge/cyberpunk2077/vehicles.dashboard-ui.yaml +203 -0
- package/knowledge/cyberpunk2077/vehicles.entities.yaml +195 -0
- package/knowledge/cyberpunk2077/vehicles.wiring.yaml +137 -0
- package/knowledge/cyberpunk2077/wolvenkit.cli.yaml +167 -0
- package/knowledge/cyberpunk2077/workspot.system.yaml +55 -0
- package/knowledge/cyberpunk2077/world.interactions.yaml +41 -0
- package/knowledge/cyberpunk2077/world.loot-container.yaml +167 -0
- package/knowledge/cyberpunk2077/world.sectors.yaml +233 -0
- package/knowledge/eldenring/formats.binders.yaml +237 -0
- package/knowledge/eldenring/launcher.me3.yaml +230 -0
- package/knowledge/eldenring/launcher.modengine2.yaml +119 -0
- package/knowledge/eldenring/params.paramdef.yaml +149 -0
- package/knowledge/eldenring/params.regulation.yaml +142 -0
- package/knowledge/eldenring/saves.online.yaml +118 -0
- package/knowledge/eldenring/toolchain.cli.yaml +256 -0
- package/knowledge/nivalisnights/modding.runtime.yaml +157 -0
- package/knowledge/skyrimse/archives.bsa.yaml +212 -0
- package/knowledge/skyrimse/install.detection.yaml +41 -0
- package/knowledge/skyrimse/loadorder.files.yaml +242 -0
- package/knowledge/skyrimse/plugins.esl.yaml +189 -0
- package/knowledge/skyrimse/plugins.format.yaml +224 -0
- package/knowledge/skyrimse/skse.runtime.yaml +283 -0
- package/knowledge/skyrimse/toolchain.xedit-spriggit.yaml +336 -0
- package/knowledge/stardewvalley/contentpatcher.format.yaml +338 -0
- package/knowledge/stardewvalley/distribution.channels.yaml +236 -0
- package/knowledge/stardewvalley/game.versions.yaml +202 -0
- package/knowledge/stardewvalley/install.layout.yaml +277 -0
- package/knowledge/stardewvalley/smapi.loader.yaml +284 -0
- package/knowledge/stardewvalley/smapi.logs.yaml +329 -0
- package/knowledge/stardewvalley/smapi.manifest.yaml +293 -0
- package/knowledge/stardewvalley/toolchain.build.yaml +261 -0
- package/knowledge/stardewvalley/wiki.dataformat.yaml +130 -0
- package/knowledge/subnautica2/game.build.yaml +331 -0
- package/knowledge/subnautica2/modding.state.yaml +210 -0
- package/knowledge/subnautica2/ue5.containers.yaml +72 -0
- package/knowledge/subnautica2/ue5.paks.yaml +203 -0
- package/knowledge/subnautica2/ue5.tooling.yaml +175 -0
- package/knowledge/subnautica2/ue5.ue4ss.yaml +203 -0
- package/knowledge/valheim/bepinex.loader.yaml +247 -0
- package/knowledge/valheim/bepinex.plugins.yaml +251 -0
- package/knowledge/valheim/game.versions.yaml +321 -0
- package/knowledge/valheim/install.layout.yaml +216 -0
- package/knowledge/valheim/jotunn.library.yaml +334 -0
- package/knowledge/valheim/thunderstore.packaging.yaml +217 -0
- package/knowledge/valheim/toolchain.build.yaml +111 -0
- package/package.json +29 -0
- package/scaffolds/baldursgate3/new-class-injection.yaml +148 -0
- package/scaffolds/baldursgate3/new-item.yaml +264 -0
- package/scaffolds/baldursgate3/new-mod.yaml +174 -0
- package/scaffolds/baldursgate3/new-passive.yaml +100 -0
- package/scaffolds/baldursgate3/new-project.yaml +118 -0
- package/scaffolds/baldursgate3/new-quest-stub.yaml +159 -0
- package/scaffolds/baldursgate3/new-spell.yaml +102 -0
- package/scaffolds/baldursgate3/new-status.yaml +119 -0
- package/scaffolds/baldursgate3/sequences/subclass-kit.yaml +50 -0
- package/scaffolds/cyberpunk2077/edit-tweak.yaml +70 -0
- package/scaffolds/cyberpunk2077/mesh-export-preset.yaml +170 -0
- package/scaffolds/cyberpunk2077/new-entity-patch.yaml +255 -0
- package/scaffolds/cyberpunk2077/new-garment-refit.yaml +279 -0
- package/scaffolds/cyberpunk2077/new-item-chain.yaml +548 -0
- package/scaffolds/cyberpunk2077/new-localization.yaml +130 -0
- package/scaffolds/cyberpunk2077/new-mod-settings.yaml +133 -0
- package/scaffolds/cyberpunk2077/new-player-replacer.yaml +1003 -0
- package/scaffolds/cyberpunk2077/new-project.yaml +157 -0
- package/scaffolds/cyberpunk2077/new-sector-node.yaml +156 -0
- package/scaffolds/cyberpunk2077/new-tweak.yaml +121 -0
- package/scaffolds/cyberpunk2077/new-vehicle-livery.yaml +148 -0
- package/scaffolds/cyberpunk2077/new-workspot-entity.yaml +177 -0
- package/scaffolds/cyberpunk2077/sequences/iconic-weapon.yaml +72 -0
- package/scaffolds/cyberpunk2077/sequences/quickhack-takeover.yaml +213 -0
- package/scaffolds/stardewvalley/new-cp-patch.yaml +106 -0
- package/scaffolds/stardewvalley/new-csharp-mod.yaml +195 -0
- package/scaffolds/stardewvalley/new-project.yaml +184 -0
- package/scaffolds/stardewvalley/sequences/content-pack.yaml +31 -0
- package/scripts/mw-tool.mjs +184 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { parse as parseYaml } from "yaml";
|
|
4
|
+
import { walk } from "../../../core/fsutil.js";
|
|
5
|
+
import { globToRegExp } from "../../../core/validate/context.js";
|
|
6
|
+
const GAME = "cyberpunk2077";
|
|
7
|
+
const GROUPS = ["history"];
|
|
8
|
+
const MOD_TYPES = ["packaging"];
|
|
9
|
+
function isProjectTarget(target) {
|
|
10
|
+
return target.kind === "project";
|
|
11
|
+
}
|
|
12
|
+
function extensionOf(p) {
|
|
13
|
+
const idx = p.lastIndexOf(".");
|
|
14
|
+
return idx === -1 ? "" : p.slice(idx).toLowerCase();
|
|
15
|
+
}
|
|
16
|
+
function normalizeSlashes(p) {
|
|
17
|
+
return p.replace(/\\/g, "/").toLowerCase();
|
|
18
|
+
}
|
|
19
|
+
function isRecord(v) {
|
|
20
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
21
|
+
}
|
|
22
|
+
function buildExcludeMatchers(project) {
|
|
23
|
+
return (project.build?.exclude ?? []).map(globToRegExp);
|
|
24
|
+
}
|
|
25
|
+
function matchesAny(relPath, matchers) {
|
|
26
|
+
const norm = normalizeSlashes(relPath);
|
|
27
|
+
return matchers.some((re) => re.test(norm));
|
|
28
|
+
}
|
|
29
|
+
function isWithin(parent, child) {
|
|
30
|
+
const rel = path.relative(parent, child);
|
|
31
|
+
return rel === "" || (!(rel === ".." || rel.startsWith(`..${path.sep}`)) && !path.isAbsolute(rel));
|
|
32
|
+
}
|
|
33
|
+
function isWithinAnySource(absPath, project) {
|
|
34
|
+
return project.sources.some((s) => s.exists && isWithin(s.absPath, absPath));
|
|
35
|
+
}
|
|
36
|
+
const DELETE_BEFORE_SHIP_RE = /delete\s+before\s+ship/i;
|
|
37
|
+
const DEV_SCRIPT_SCAN_EXTENSIONS = new Set([".reds", ".lua", ".py", ".ps1"]);
|
|
38
|
+
const DEV_SCRIPTS_ID = "cp2077.history.dev-scripts-never-ship";
|
|
39
|
+
const DEV_SCRIPTS_EVIDENCE = [
|
|
40
|
+
"cp2077.packaging.variants.dev-scripts-that-log-every-attack-must-never-ship",
|
|
41
|
+
"cp2077.packaging.variants.build-script-gates-source-and-dev-script-leakage",
|
|
42
|
+
];
|
|
43
|
+
export const devScriptsNeverShipValidator = {
|
|
44
|
+
id: DEV_SCRIPTS_ID,
|
|
45
|
+
game: GAME,
|
|
46
|
+
groups: GROUPS,
|
|
47
|
+
modTypes: MOD_TYPES,
|
|
48
|
+
summary: "Flags a dev-only file (headed \"DELETE BEFORE SHIP\") that build.exclude does not cover, and any build.exclude-matched file that has leaked into an already-built outDir/staging tree — the exact near-miss a pre-release audit caught live on a real mod.",
|
|
49
|
+
appliesTo: isProjectTarget,
|
|
50
|
+
appliesToDescription: "the whole project: every source tree (for the dev-marker check) and build.outDir/build.staging when they already exist on disk (for the leaked-into-output check)",
|
|
51
|
+
needs: { project: true },
|
|
52
|
+
cost: "build",
|
|
53
|
+
severity: "block",
|
|
54
|
+
evidence: DEV_SCRIPTS_EVIDENCE,
|
|
55
|
+
async run(ctx) {
|
|
56
|
+
const project = ctx.project;
|
|
57
|
+
const matchers = buildExcludeMatchers(project);
|
|
58
|
+
const findings = [];
|
|
59
|
+
for (const source of project.sources) {
|
|
60
|
+
if (!source.exists)
|
|
61
|
+
continue;
|
|
62
|
+
const { files, truncated } = await walk(source.absPath);
|
|
63
|
+
for (const rel of files) {
|
|
64
|
+
if (!DEV_SCRIPT_SCAN_EXTENSIONS.has(extensionOf(rel)))
|
|
65
|
+
continue;
|
|
66
|
+
const abs = path.join(source.absPath, rel);
|
|
67
|
+
let text;
|
|
68
|
+
try {
|
|
69
|
+
text = await ctx.readText(abs);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (DELETE_BEFORE_SHIP_RE.test(text) && !matchesAny(rel, matchers)) {
|
|
75
|
+
findings.push({
|
|
76
|
+
validatorId: DEV_SCRIPTS_ID,
|
|
77
|
+
severity: "block",
|
|
78
|
+
file: abs,
|
|
79
|
+
message: `This file is headed "DELETE BEFORE SHIP" but is not covered by any build.exclude pattern, so nothing will stop it from shipping. A real audit found exactly this class of file (a mod's _Debug.reds and _Probe.reds) still deployed in the live game install.`,
|
|
80
|
+
fix: `Add a build.exclude pattern that matches "${source.id}/${rel}" (e.g. "**/${path.basename(rel)}"), or delete the file if it is no longer needed.`,
|
|
81
|
+
evidence: DEV_SCRIPTS_EVIDENCE,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (truncated) {
|
|
86
|
+
findings.push({
|
|
87
|
+
validatorId: DEV_SCRIPTS_ID,
|
|
88
|
+
severity: "info",
|
|
89
|
+
file: source.absPath,
|
|
90
|
+
message: `Source "${source.id}" has more files than this scan's limit; the dev-marker scan may be incomplete for it.`,
|
|
91
|
+
evidence: DEV_SCRIPTS_EVIDENCE,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const roots = [];
|
|
96
|
+
if (project.build?.outDirExists && project.build.absOutDir) {
|
|
97
|
+
roots.push({ label: "build.outDir", abs: project.build.absOutDir });
|
|
98
|
+
}
|
|
99
|
+
if (project.build?.absStaging) {
|
|
100
|
+
roots.push({ label: "build.staging", abs: project.build.absStaging });
|
|
101
|
+
}
|
|
102
|
+
for (const root of roots) {
|
|
103
|
+
const { files, truncated } = await walk(root.abs);
|
|
104
|
+
for (const rel of files) {
|
|
105
|
+
const abs = path.join(root.abs, rel);
|
|
106
|
+
if (isWithinAnySource(abs, project))
|
|
107
|
+
continue;
|
|
108
|
+
if (matchesAny(rel, matchers)) {
|
|
109
|
+
findings.push({
|
|
110
|
+
validatorId: DEV_SCRIPTS_ID,
|
|
111
|
+
severity: "block",
|
|
112
|
+
file: abs,
|
|
113
|
+
message: `"${rel}" matches a build.exclude pattern but is present in ${root.label} (${root.abs}). Either the build step did not filter it out, or it was copied back in after packing — it must not reach the deployable output.`,
|
|
114
|
+
fix: `Remove this file from ${root.label}, or re-run the build so its own exclude sweep drops it before packing.`,
|
|
115
|
+
evidence: DEV_SCRIPTS_EVIDENCE,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (truncated) {
|
|
120
|
+
findings.push({
|
|
121
|
+
validatorId: DEV_SCRIPTS_ID,
|
|
122
|
+
severity: "info",
|
|
123
|
+
file: root.abs,
|
|
124
|
+
message: `${root.label} has more files than this scan's limit; the leaked-exclude scan may be incomplete for it.`,
|
|
125
|
+
evidence: DEV_SCRIPTS_EVIDENCE,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return findings;
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
const CLEAN_PACK_ID = "cp2077.history.clean-pack-json-sweep";
|
|
133
|
+
const JSON_SIDECAR_EVIDENCE = "cp2077.packaging.variants.build-sources-pack-silently-as-json";
|
|
134
|
+
const DEAD_EFFECT_EVIDENCE = "cp2077.packaging.variants.dead-referenced-fx-resource-must-be-pruned";
|
|
135
|
+
const CLEAN_PACK_EVIDENCE = [
|
|
136
|
+
JSON_SIDECAR_EVIDENCE,
|
|
137
|
+
DEAD_EFFECT_EVIDENCE,
|
|
138
|
+
"cp2077.process.assetbuild.stale-serialized-sidecar-can-resurrect-old-geometry",
|
|
139
|
+
];
|
|
140
|
+
async function localizationBasenames(project, ctx) {
|
|
141
|
+
const xlSource = project.sources.find((s) => s.kind === "xl" && s.exists);
|
|
142
|
+
if (!xlSource)
|
|
143
|
+
return new Set();
|
|
144
|
+
let text;
|
|
145
|
+
try {
|
|
146
|
+
text = await ctx.readText(xlSource.absPath);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return new Set();
|
|
150
|
+
}
|
|
151
|
+
let doc;
|
|
152
|
+
try {
|
|
153
|
+
doc = parseYaml(text.replace(/^\uFEFF/, ""));
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return new Set();
|
|
157
|
+
}
|
|
158
|
+
if (!isRecord(doc) || !isRecord(doc.localization) || !isRecord(doc.localization.onscreens))
|
|
159
|
+
return new Set();
|
|
160
|
+
const names = new Set();
|
|
161
|
+
for (const value of Object.values(doc.localization.onscreens)) {
|
|
162
|
+
if (typeof value === "string")
|
|
163
|
+
names.add(path.posix.basename(normalizeSlashes(value)));
|
|
164
|
+
}
|
|
165
|
+
return names;
|
|
166
|
+
}
|
|
167
|
+
export const cleanPackJsonSweepValidator = {
|
|
168
|
+
id: CLEAN_PACK_ID,
|
|
169
|
+
game: GAME,
|
|
170
|
+
groups: GROUPS,
|
|
171
|
+
modTypes: MOD_TYPES,
|
|
172
|
+
summary: "Sweeps archive-staging for a leaked build-source .json (the *.mesh.json/*.app.json/*.effect.json class that packs silently, since .json is a real game resource type) not covered by build.exclude, and for a dead effect resource no .app in the tree references.",
|
|
173
|
+
appliesTo: isProjectTarget,
|
|
174
|
+
appliesToDescription: "every project source of kind \"archive-staging\"",
|
|
175
|
+
needs: { project: true },
|
|
176
|
+
cost: "build",
|
|
177
|
+
severity: "block",
|
|
178
|
+
evidence: CLEAN_PACK_EVIDENCE,
|
|
179
|
+
async run(ctx) {
|
|
180
|
+
const project = ctx.project;
|
|
181
|
+
const matchers = buildExcludeMatchers(project);
|
|
182
|
+
const findings = [];
|
|
183
|
+
const stagingSources = project.sources.filter((s) => s.kind === "archive-staging" && s.exists);
|
|
184
|
+
if (stagingSources.length === 0)
|
|
185
|
+
return findings;
|
|
186
|
+
const locNames = await localizationBasenames(project, ctx);
|
|
187
|
+
for (const source of stagingSources) {
|
|
188
|
+
const { files, truncated } = await walk(source.absPath);
|
|
189
|
+
for (const rel of files) {
|
|
190
|
+
if (extensionOf(rel) !== ".json")
|
|
191
|
+
continue;
|
|
192
|
+
const base = path.posix.basename(normalizeSlashes(rel));
|
|
193
|
+
if (locNames.has(base))
|
|
194
|
+
continue;
|
|
195
|
+
if (matchesAny(rel, matchers))
|
|
196
|
+
continue;
|
|
197
|
+
findings.push({
|
|
198
|
+
validatorId: CLEAN_PACK_ID,
|
|
199
|
+
severity: "block",
|
|
200
|
+
file: path.join(source.absPath, rel),
|
|
201
|
+
message: `"${rel}" is a build-source .json left inside archive-staging ("${source.id}"). Unlike a skipped format such as .png, .json is a real game resource type and the packer includes it SILENTLY — this is exactly how a real release shipped 1.32 MB of *.mesh.json/*.app.json sources by accident.`,
|
|
202
|
+
fix: `Delete this file from the staging tree before packing, or add a build.exclude pattern for it if it must remain in the working tree (e.g. "**/${path.basename(rel)}").`,
|
|
203
|
+
evidence: [JSON_SIDECAR_EVIDENCE],
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
const effectFiles = files.filter((f) => extensionOf(f) === ".effect");
|
|
207
|
+
if (effectFiles.length > 0) {
|
|
208
|
+
const appFiles = files.filter((f) => extensionOf(f) === ".app");
|
|
209
|
+
const appTexts = await Promise.all(appFiles.map(async (f) => {
|
|
210
|
+
try {
|
|
211
|
+
return (await ctx.readText(path.join(source.absPath, f))).toLowerCase();
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return "";
|
|
215
|
+
}
|
|
216
|
+
}));
|
|
217
|
+
for (const ef of effectFiles) {
|
|
218
|
+
const stem = path.posix.basename(ef, ".effect").toLowerCase();
|
|
219
|
+
const referenced = appTexts.some((t) => t.includes(stem));
|
|
220
|
+
if (!referenced) {
|
|
221
|
+
const excluded = matchesAny(ef, matchers);
|
|
222
|
+
findings.push({
|
|
223
|
+
validatorId: CLEAN_PACK_ID,
|
|
224
|
+
severity: "warn",
|
|
225
|
+
file: path.join(source.absPath, ef),
|
|
226
|
+
message: `"${ef}" is staged in archive-staging ("${source.id}") but the string "${stem}" does not appear in any .app in this tree — it looks like a dead effect resource left over from a reverted design (the documented case: a weapon .app reverted to a vanilla effect, leaving its cloned effect unreferenced).${excluded
|
|
227
|
+
? " It is already covered by build.exclude, so it will not ship, but it remains dead clutter in the working tree — confirm and delete it."
|
|
228
|
+
: " It is NOT covered by build.exclude, so it may still ship despite being unreferenced."}`,
|
|
229
|
+
fix: `Verify no .app references "${stem}" (search the whole shipping tree, not just this source), then delete the effect resource — or, if it is a real work-in-progress asset, add a build.exclude entry for it.`,
|
|
230
|
+
evidence: [DEAD_EFFECT_EVIDENCE],
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (truncated) {
|
|
236
|
+
findings.push({
|
|
237
|
+
validatorId: CLEAN_PACK_ID,
|
|
238
|
+
severity: "info",
|
|
239
|
+
file: source.absPath,
|
|
240
|
+
message: `Source "${source.id}" has more files than this scan's limit; the clean-pack sweep may be incomplete for it.`,
|
|
241
|
+
evidence: CLEAN_PACK_EVIDENCE,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return findings;
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
const ARCHIVE_SORT_ID = "cp2077.history.archive-name-sort-order";
|
|
249
|
+
const ARCHIVE_SORT_EVIDENCE = [
|
|
250
|
+
"cp2077.loadorder.archive-conflicts-first-wins",
|
|
251
|
+
"cp2077.loadorder.reskin-ships-as-override-archive",
|
|
252
|
+
"cp2077.loadorder.archive-xl-does-not-reprioritise",
|
|
253
|
+
];
|
|
254
|
+
function archiveTargetForSource(targets, source) {
|
|
255
|
+
const lastSeg = path.basename(source.path).toLowerCase();
|
|
256
|
+
return targets.find((t) => {
|
|
257
|
+
if (!t.from.toLowerCase().endsWith(".archive"))
|
|
258
|
+
return false;
|
|
259
|
+
const stem = path.basename(t.from, path.extname(t.from)).toLowerCase();
|
|
260
|
+
return stem.includes(lastSeg) || lastSeg.includes(stem) || stem.includes(source.id.toLowerCase());
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
export const archiveNameSortOrderValidator = {
|
|
264
|
+
id: ARCHIVE_SORT_ID,
|
|
265
|
+
game: GAME,
|
|
266
|
+
groups: GROUPS,
|
|
267
|
+
modTypes: MOD_TYPES,
|
|
268
|
+
summary: "When two archive-staging sources demonstrably supply the same depot path, checks that the smaller (override/reskin) one is named to sort ASCII-before the larger (base) one — first-wins, not last-wins.",
|
|
269
|
+
appliesTo: isProjectTarget,
|
|
270
|
+
appliesToDescription: "the whole project, only when it declares two or more sources of kind \"archive-staging\" whose files overlap",
|
|
271
|
+
needs: { project: true, install: true },
|
|
272
|
+
cost: "build",
|
|
273
|
+
severity: "warn",
|
|
274
|
+
evidence: ARCHIVE_SORT_EVIDENCE,
|
|
275
|
+
async run(ctx) {
|
|
276
|
+
const project = ctx.project;
|
|
277
|
+
const findings = [];
|
|
278
|
+
const stagingSources = project.sources.filter((s) => s.kind === "archive-staging" && s.exists);
|
|
279
|
+
if (stagingSources.length < 2)
|
|
280
|
+
return findings;
|
|
281
|
+
const fileSets = new Map();
|
|
282
|
+
for (const s of stagingSources) {
|
|
283
|
+
const { files } = await walk(s.absPath);
|
|
284
|
+
fileSets.set(s.id, new Set(files.map((f) => normalizeSlashes(f))));
|
|
285
|
+
}
|
|
286
|
+
const targets = project.project.deploy?.targets ?? [];
|
|
287
|
+
for (let i = 0; i < stagingSources.length; i++) {
|
|
288
|
+
for (let j = i + 1; j < stagingSources.length; j++) {
|
|
289
|
+
const a = stagingSources[i];
|
|
290
|
+
const b = stagingSources[j];
|
|
291
|
+
if (!a || !b)
|
|
292
|
+
continue;
|
|
293
|
+
const setA = fileSets.get(a.id);
|
|
294
|
+
const setB = fileSets.get(b.id);
|
|
295
|
+
const overlaps = [...setA].some((p) => setB.has(p));
|
|
296
|
+
if (!overlaps)
|
|
297
|
+
continue;
|
|
298
|
+
const countA = setA.size;
|
|
299
|
+
const countB = setB.size;
|
|
300
|
+
if (countA === countB)
|
|
301
|
+
continue;
|
|
302
|
+
const overrideSource = countA < countB ? a : b;
|
|
303
|
+
const baseSource = countA < countB ? b : a;
|
|
304
|
+
const overrideTarget = archiveTargetForSource(targets, overrideSource);
|
|
305
|
+
const baseTarget = archiveTargetForSource(targets, baseSource);
|
|
306
|
+
if (!overrideTarget || !baseTarget)
|
|
307
|
+
continue;
|
|
308
|
+
const overrideName = path.basename(overrideTarget.from);
|
|
309
|
+
const baseName = path.basename(baseTarget.from);
|
|
310
|
+
if (!(overrideName.toLowerCase() < baseName.toLowerCase())) {
|
|
311
|
+
findings.push({
|
|
312
|
+
validatorId: ARCHIVE_SORT_ID,
|
|
313
|
+
severity: "warn",
|
|
314
|
+
file: project.file,
|
|
315
|
+
message: `"${overrideSource.id}" (${overrideSource.absPath}, the smaller of the two, ${countA < countB ? countA : countB} files) shares at least one depot path with "${baseSource.id}" (${baseSource.absPath}, ${countA < countB ? countB : countA} files), so it looks like an override/reskin archive. Archive load order is ASCII-alphabetical and the FIRST-sorting archive wins — but its deploy target's filename "${overrideName}" does not sort before the base archive's "${baseName}". Vortex's "z-prefix"/last-wins advice is wrong for Cyberpunk 2077 archives, and a .archive.xl does not re-prioritise its .archive either.`,
|
|
316
|
+
fix: `Rename the override archive's deploy target so it sorts ASCII-before "${baseName}" — a space right after the shared stem works cleanly (space 0x20 sorts before "." 0x2E), e.g. "${baseName.replace(/\.archive$/i, "")} - Override.archive".`,
|
|
317
|
+
evidence: ARCHIVE_SORT_EVIDENCE,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return findings;
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
const DEPLOY_SET_ID = "cp2077.history.deploy-set-completeness";
|
|
326
|
+
const DEPLOY_SET_EVIDENCE = [
|
|
327
|
+
"cp2077.world.loot-container.three-artifacts-must-deploy-together",
|
|
328
|
+
"cp2077.archivexl.manifest-registers-at-init",
|
|
329
|
+
];
|
|
330
|
+
export const deploySetCompletenessValidator = {
|
|
331
|
+
id: DEPLOY_SET_ID,
|
|
332
|
+
game: GAME,
|
|
333
|
+
groups: GROUPS,
|
|
334
|
+
modTypes: MOD_TYPES,
|
|
335
|
+
summary: "A project whose .xl registers streaming, factories or localization must declare deploy targets for the archive, the .xl and the tweaks together — redeploying only some of the three leaves ArchiveXL registering against a stale sibling.",
|
|
336
|
+
appliesTo: isProjectTarget,
|
|
337
|
+
appliesToDescription: "the whole project, only when its .xl source registers streaming, factories or localization",
|
|
338
|
+
needs: { project: true },
|
|
339
|
+
cost: "edit",
|
|
340
|
+
severity: "block",
|
|
341
|
+
evidence: DEPLOY_SET_EVIDENCE,
|
|
342
|
+
async run(ctx) {
|
|
343
|
+
const project = ctx.project;
|
|
344
|
+
const xlSource = project.sources.find((s) => s.kind === "xl" && s.exists);
|
|
345
|
+
if (!xlSource)
|
|
346
|
+
return [];
|
|
347
|
+
let text;
|
|
348
|
+
try {
|
|
349
|
+
text = await ctx.readText(xlSource.absPath);
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
return [];
|
|
353
|
+
}
|
|
354
|
+
let doc;
|
|
355
|
+
try {
|
|
356
|
+
doc = parseYaml(text.replace(/^\uFEFF/, ""));
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
return [];
|
|
360
|
+
}
|
|
361
|
+
if (!isRecord(doc))
|
|
362
|
+
return [];
|
|
363
|
+
const registeredKeys = ["streaming", "factories", "localization"].filter((k) => doc[k] !== undefined);
|
|
364
|
+
if (registeredKeys.length === 0)
|
|
365
|
+
return [];
|
|
366
|
+
const targets = project.project.deploy?.targets ?? [];
|
|
367
|
+
const hasArchive = targets.some((t) => t.from.toLowerCase().endsWith(".archive"));
|
|
368
|
+
const hasXl = targets.some((t) => t.from.toLowerCase().endsWith(".xl"));
|
|
369
|
+
const tweaksSources = project.sources.filter((s) => s.kind === "tweaks");
|
|
370
|
+
const insideTweaks = (from) => {
|
|
371
|
+
const candidates = path.isAbsolute(from)
|
|
372
|
+
? [from]
|
|
373
|
+
: [...(project.build ? [path.join(project.build.absOutDir, from)] : []), path.join(project.root, from)];
|
|
374
|
+
return candidates.some((abs) => tweaksSources.some((s) => {
|
|
375
|
+
const rel = path.relative(s.absPath, abs);
|
|
376
|
+
return rel === "" || (!(rel === ".." || rel.startsWith(`..${path.sep}`)) && !path.isAbsolute(rel));
|
|
377
|
+
}));
|
|
378
|
+
};
|
|
379
|
+
const hasTweaks = targets.some((t) => tweaksSources.some((s) => s.id === t.from) || insideTweaks(t.from));
|
|
380
|
+
const missing = [];
|
|
381
|
+
if (!hasArchive)
|
|
382
|
+
missing.push("the packed .archive");
|
|
383
|
+
if (!hasXl)
|
|
384
|
+
missing.push("the .archive.xl manifest");
|
|
385
|
+
if (!hasTweaks)
|
|
386
|
+
missing.push("the tweaks (TweakXL yaml) source");
|
|
387
|
+
if (missing.length === 0)
|
|
388
|
+
return [];
|
|
389
|
+
return [
|
|
390
|
+
{
|
|
391
|
+
validatorId: DEPLOY_SET_ID,
|
|
392
|
+
severity: "block",
|
|
393
|
+
file: project.file,
|
|
394
|
+
message: `This project's .xl manifest ("${xlSource.id}") registers ${registeredKeys.map((k) => `"${k}:"`).join(", ")}, but deploy.targets has no target for: ${missing.join(", ")}. Redeploying the archive while leaving an older sibling behind (or vice versa) makes ArchiveXL's registration for it silently fail to appear — the archive, the .xl and the tweaks must deploy together.`,
|
|
395
|
+
fix: `Add a deploy target for ${missing.join(" and ")} alongside the existing ones in deploy.targets.`,
|
|
396
|
+
evidence: DEPLOY_SET_EVIDENCE,
|
|
397
|
+
},
|
|
398
|
+
];
|
|
399
|
+
},
|
|
400
|
+
};
|
|
401
|
+
const FOLDER_PRUNE_ID = "cp2077.history.folder-target-prunes";
|
|
402
|
+
const FOLDER_PRUNE_EVIDENCE = ["observed (2026-09-28): a folder deploy left a renamed .yaml behind in r6/tweaks, where TweakXL still read it", "cp2077.tweakxl.yaml-syntax.files-read-in-name-order"];
|
|
403
|
+
async function isDirectory(p) {
|
|
404
|
+
try {
|
|
405
|
+
return (await fs.stat(p)).isDirectory();
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
export const folderTargetPrunesValidator = {
|
|
412
|
+
id: FOLDER_PRUNE_ID,
|
|
413
|
+
game: GAME,
|
|
414
|
+
groups: GROUPS,
|
|
415
|
+
modTypes: MOD_TYPES,
|
|
416
|
+
summary: "A deploy target copying a folder into its own subfolder declares a prune, so a renamed or deleted file does not stay deployed.",
|
|
417
|
+
appliesTo: isProjectTarget,
|
|
418
|
+
appliesToDescription: "the whole project's deploy.targets",
|
|
419
|
+
needs: { project: true },
|
|
420
|
+
cost: "edit",
|
|
421
|
+
severity: "warn",
|
|
422
|
+
evidence: FOLDER_PRUNE_EVIDENCE,
|
|
423
|
+
async run(ctx) {
|
|
424
|
+
const project = ctx.project;
|
|
425
|
+
const out = [];
|
|
426
|
+
for (const t of project.project.deploy?.targets ?? []) {
|
|
427
|
+
if (!t.into || (t.prune && t.prune.length > 0))
|
|
428
|
+
continue;
|
|
429
|
+
const source = project.sources.find((s) => s.id === t.from);
|
|
430
|
+
const candidates = source
|
|
431
|
+
? [source.absPath]
|
|
432
|
+
: path.isAbsolute(t.from)
|
|
433
|
+
? [t.from]
|
|
434
|
+
: [...(project.build ? [path.join(project.build.absOutDir, t.from)] : []), path.join(project.root, t.from)];
|
|
435
|
+
let folder = false;
|
|
436
|
+
for (const c of candidates) {
|
|
437
|
+
if (await isDirectory(c)) {
|
|
438
|
+
folder = true;
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
if (!folder)
|
|
443
|
+
continue;
|
|
444
|
+
const into = t.into.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
445
|
+
out.push({
|
|
446
|
+
validatorId: FOLDER_PRUNE_ID,
|
|
447
|
+
severity: "warn",
|
|
448
|
+
file: project.file,
|
|
449
|
+
message: `Deploy target "${t.from}" -> ${t.rootId}/${into} copies a folder and declares no prune, so a file renamed or removed in "${t.from}" stays deployed and keeps loading (observed: a renamed TweakXL file's old copy stayed in r6\\tweaks).`,
|
|
450
|
+
fix: `Add "prune": ["${into}/**"] to this target.`,
|
|
451
|
+
evidence: FOLDER_PRUNE_EVIDENCE,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
return out;
|
|
455
|
+
},
|
|
456
|
+
};
|
|
457
|
+
export const PACKAGING_VALIDATORS = [
|
|
458
|
+
devScriptsNeverShipValidator,
|
|
459
|
+
cleanPackJsonSweepValidator,
|
|
460
|
+
archiveNameSortOrderValidator,
|
|
461
|
+
deploySetCompletenessValidator,
|
|
462
|
+
folderTargetPrunesValidator,
|
|
463
|
+
];
|