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,172 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { copyFilePreservingMtime, readManifest, sha256File } from "./backup.js";
|
|
4
|
+
import { isDirectory } from "../fsutil.js";
|
|
5
|
+
export async function listBackups(backupRoot) {
|
|
6
|
+
let entries;
|
|
7
|
+
try {
|
|
8
|
+
entries = await fs.readdir(backupRoot, { withFileTypes: true });
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
const out = [];
|
|
14
|
+
for (const entry of entries) {
|
|
15
|
+
if (!entry.isDirectory())
|
|
16
|
+
continue;
|
|
17
|
+
const dir = path.join(backupRoot, entry.name);
|
|
18
|
+
try {
|
|
19
|
+
const manifest = await readManifest(dir);
|
|
20
|
+
const links = manifest.entries.filter((e) => "kind" in e);
|
|
21
|
+
const sizeBytes = manifest.entries.reduce((sum, e) => sum + ("kind" in e ? 0 : e.sizeBytes), 0);
|
|
22
|
+
out.push({
|
|
23
|
+
id: entry.name,
|
|
24
|
+
path: dir,
|
|
25
|
+
purpose: manifest.purpose,
|
|
26
|
+
time: manifest.time,
|
|
27
|
+
entryCount: manifest.entries.length,
|
|
28
|
+
linkEntryCount: links.length,
|
|
29
|
+
sizeBytes,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
out.push({ id: entry.name, path: dir, entryCount: 0, linkEntryCount: 0, sizeBytes: 0, error: errorMessage(err) });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return out.sort((a, b) => (a.id < b.id ? 1 : a.id > b.id ? -1 : 0));
|
|
37
|
+
}
|
|
38
|
+
function errorMessage(err) {
|
|
39
|
+
return err instanceof Error ? err.message : String(err);
|
|
40
|
+
}
|
|
41
|
+
async function restoreLinkEntry(entry) {
|
|
42
|
+
let existing;
|
|
43
|
+
try {
|
|
44
|
+
existing = await fs.lstat(entry.originalPath);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
existing = undefined;
|
|
48
|
+
}
|
|
49
|
+
if (existing?.isSymbolicLink()) {
|
|
50
|
+
await fs.rm(entry.originalPath, { recursive: true, force: true });
|
|
51
|
+
}
|
|
52
|
+
else if (existing) {
|
|
53
|
+
throw new Error(`${entry.originalPath} is now a real ${existing.isDirectory() ? "directory" : "file"}, not a link; ` +
|
|
54
|
+
"refusing to replace it with the backed-up link. Move it aside and restore again.");
|
|
55
|
+
}
|
|
56
|
+
await fs.mkdir(path.dirname(entry.originalPath), { recursive: true });
|
|
57
|
+
const targetIsDir = await isDirectory(entry.linkTarget);
|
|
58
|
+
await fs.symlink(entry.linkTarget, entry.originalPath, process.platform === "win32" && targetIsDir ? "junction" : undefined);
|
|
59
|
+
}
|
|
60
|
+
export function isLockError(err) {
|
|
61
|
+
const code = err?.code;
|
|
62
|
+
return code === "EPERM" || code === "EBUSY" || code === "EACCES";
|
|
63
|
+
}
|
|
64
|
+
function backupFileFor(backupDir, backupPath) {
|
|
65
|
+
const src = path.resolve(backupDir, ...backupPath.split("/"));
|
|
66
|
+
const rel = path.relative(backupDir, src);
|
|
67
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) {
|
|
68
|
+
throw new Error(`manifest entry names ${JSON.stringify(backupPath)}, which is not inside the backup directory ${backupDir}; refusing to restore from it`);
|
|
69
|
+
}
|
|
70
|
+
return src;
|
|
71
|
+
}
|
|
72
|
+
function restoreConfidence(backupDir) {
|
|
73
|
+
return {
|
|
74
|
+
level: "high",
|
|
75
|
+
assumptions: [`manifest.json under ${backupDir} has not been hand-edited since it was written`],
|
|
76
|
+
verifiedBy: ["manifest.json", "sha256 of each backed-up file, re-checked immediately before restoring it"],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export async function restoreBackup(backupDir, options = {}) {
|
|
80
|
+
const mode = options.mode ?? "dry-run";
|
|
81
|
+
if (!path.isAbsolute(backupDir)) {
|
|
82
|
+
throw new Error(`safety: backupDir must be an absolute path, got "${backupDir}"`);
|
|
83
|
+
}
|
|
84
|
+
const manifest = await readManifest(backupDir);
|
|
85
|
+
for (const entry of manifest.entries) {
|
|
86
|
+
if (!("kind" in entry))
|
|
87
|
+
backupFileFor(backupDir, entry.backupPath);
|
|
88
|
+
}
|
|
89
|
+
if (mode === "dry-run") {
|
|
90
|
+
const operations = [];
|
|
91
|
+
for (const entry of manifest.entries) {
|
|
92
|
+
if ("kind" in entry) {
|
|
93
|
+
const op = { kind: "junction", path: entry.originalPath, target: entry.linkTarget };
|
|
94
|
+
operations.push({ op, status: "planned" });
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const op = { kind: "overwrite", path: entry.originalPath, content: new Uint8Array(0) };
|
|
98
|
+
operations.push({ op, status: "planned", sizeBytes: entry.sizeBytes, sha256: entry.sha256 });
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
mode,
|
|
102
|
+
purpose: `restore: ${manifest.purpose}`,
|
|
103
|
+
applied: false,
|
|
104
|
+
operations,
|
|
105
|
+
confidence: restoreConfidence(backupDir),
|
|
106
|
+
next: `Nothing was changed. Re-run restoreBackup(backupDir, { mode: "apply" }) to write ` +
|
|
107
|
+
`these ${manifest.entries.length} file(s) back to their original locations.`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const results = [];
|
|
111
|
+
const locked = [];
|
|
112
|
+
let stopped = false;
|
|
113
|
+
for (const entry of manifest.entries) {
|
|
114
|
+
if ("kind" in entry) {
|
|
115
|
+
const op = { kind: "junction", path: entry.originalPath, target: entry.linkTarget };
|
|
116
|
+
if (stopped) {
|
|
117
|
+
results.push({ op, status: "skipped" });
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
await restoreLinkEntry(entry);
|
|
122
|
+
results.push({ op, status: "applied" });
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
if (isLockError(err))
|
|
126
|
+
locked.push(entry.originalPath);
|
|
127
|
+
else
|
|
128
|
+
stopped = true;
|
|
129
|
+
results.push({ op, status: "failed", error: errorMessage(err) });
|
|
130
|
+
}
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const op = { kind: "overwrite", path: entry.originalPath, content: new Uint8Array(0) };
|
|
134
|
+
if (stopped) {
|
|
135
|
+
results.push({ op, status: "skipped" });
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
const src = backupFileFor(backupDir, entry.backupPath);
|
|
140
|
+
const { sha256 } = await sha256File(src);
|
|
141
|
+
if (sha256 !== entry.sha256) {
|
|
142
|
+
throw new Error(`backup of ${entry.originalPath} is corrupted: manifest says sha256 ${entry.sha256}, file on disk hashes to ${sha256}`);
|
|
143
|
+
}
|
|
144
|
+
await copyFilePreservingMtime(src, entry.originalPath);
|
|
145
|
+
results.push({ op, status: "applied" });
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
if (isLockError(err))
|
|
149
|
+
locked.push(entry.originalPath);
|
|
150
|
+
else
|
|
151
|
+
stopped = true;
|
|
152
|
+
results.push({ op, status: "failed", error: errorMessage(err) });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const applied = results.every((r) => r.status === "applied" || r.status === "skipped");
|
|
156
|
+
const next = locked.length > 0
|
|
157
|
+
? `${locked.length} file(s) could not be written because something holds them (the game, if it is running): ` +
|
|
158
|
+
`${locked.join(", ")}. Everything else was restored. Close the holder and run the same restore again; ` +
|
|
159
|
+
`entries already restored are rewritten with identical content.`
|
|
160
|
+
: undefined;
|
|
161
|
+
return {
|
|
162
|
+
mode,
|
|
163
|
+
purpose: `restore: ${manifest.purpose}`,
|
|
164
|
+
applied,
|
|
165
|
+
operations: results,
|
|
166
|
+
confidence: restoreConfidence(backupDir),
|
|
167
|
+
next: next ??
|
|
168
|
+
(applied
|
|
169
|
+
? undefined
|
|
170
|
+
: "An integrity check failed before that file was restored; the backup itself is untouched, so fix the problem and retry."),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { PROJECT_FILE } from "../project/types.js";
|
|
3
|
+
export function bootstrapProject(dir, game, name) {
|
|
4
|
+
const root = path.resolve(dir);
|
|
5
|
+
const project = { schemaVersion: 2, name, game, sources: [] };
|
|
6
|
+
return { file: path.join(root, PROJECT_FILE), root, project, sources: [], warnings: [] };
|
|
7
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function summarize(loaded) {
|
|
2
|
+
return loaded.map(({ definition }) => ({
|
|
3
|
+
id: definition.id,
|
|
4
|
+
game: definition.game,
|
|
5
|
+
title: definition.title,
|
|
6
|
+
summary: definition.summary,
|
|
7
|
+
modTypes: definition.modTypes,
|
|
8
|
+
sandbox: !definition.needs.install && definition.needs.toolchain.length === 0,
|
|
9
|
+
}));
|
|
10
|
+
}
|
|
11
|
+
export function describe(loaded) {
|
|
12
|
+
const { definition } = loaded;
|
|
13
|
+
const filesWritten = [...new Set(definition.emits.filter((e) => "path" in e).map((e) => e.path))];
|
|
14
|
+
return {
|
|
15
|
+
id: definition.id,
|
|
16
|
+
game: definition.game,
|
|
17
|
+
title: definition.title,
|
|
18
|
+
summary: definition.summary,
|
|
19
|
+
modTypes: definition.modTypes,
|
|
20
|
+
needs: definition.needs,
|
|
21
|
+
inputs: definition.inputs,
|
|
22
|
+
ids: definition.ids,
|
|
23
|
+
satisfies: definition.satisfies,
|
|
24
|
+
filesWritten,
|
|
25
|
+
manual: definition.manual,
|
|
26
|
+
evidence: definition.evidence,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync, promises as fs } from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { readTextIfExists } from "../fsutil.js";
|
|
6
|
+
import { planFile, planMkdir } from "../safety/plan.js";
|
|
7
|
+
import { executePlan } from "../safety/execute.js";
|
|
8
|
+
import { loadKnowledge } from "../knowledge/facts.js";
|
|
9
|
+
const ASSETS = [
|
|
10
|
+
{ path: "skills/mw-validate.md", templateId: "modwright-validate" },
|
|
11
|
+
{ path: "skills/mw-scaffold.md", templateId: "modwright-scaffold" },
|
|
12
|
+
{ path: "skills/mw-new-spell.md", templateId: "modwright-new-spell", games: ["baldursgate3"] },
|
|
13
|
+
{ path: "skills/mw-new-item.md", templateId: "modwright-new-item", games: ["cyberpunk2077"] },
|
|
14
|
+
{ path: "skills/mw-verify.md", templateId: "modwright-verify" },
|
|
15
|
+
{ path: "skills/mw-ship.md", templateId: "modwright-ship" },
|
|
16
|
+
{ path: "skills/mw-author.md", templateId: "modwright-author" },
|
|
17
|
+
{ path: "agents/mw-explore.md", templateId: "modwright-explore" },
|
|
18
|
+
{ path: "agents/mw-rule-author.md", templateId: "modwright-rule-author" },
|
|
19
|
+
{ path: "agents/mw-scaffold-author.md", templateId: "modwright-scaffold-author" },
|
|
20
|
+
{ path: "agents/mw-fixture.md", templateId: "modwright-fixture" },
|
|
21
|
+
];
|
|
22
|
+
const SETTINGS_PATH = "settings.json";
|
|
23
|
+
const SETTINGS_TEMPLATE_ID = "modwright-settings";
|
|
24
|
+
const MANIFEST_FILE = "ASSETS.json";
|
|
25
|
+
const SETTINGS_BASELINE = { model: "sonnet", effortLevel: "high" };
|
|
26
|
+
function findPackageRoot(startDir) {
|
|
27
|
+
let dir = startDir;
|
|
28
|
+
for (;;) {
|
|
29
|
+
if (existsSync(path.join(dir, "package.json")))
|
|
30
|
+
return dir;
|
|
31
|
+
const parent = path.dirname(dir);
|
|
32
|
+
if (parent === dir)
|
|
33
|
+
throw new Error(`claude-assets: could not find package.json above ${startDir}`);
|
|
34
|
+
dir = parent;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function packageRoot() {
|
|
38
|
+
return findPackageRoot(path.dirname(fileURLToPath(import.meta.url)));
|
|
39
|
+
}
|
|
40
|
+
export function claudeAssetsRoot() {
|
|
41
|
+
return path.join(packageRoot(), "assets", "claude");
|
|
42
|
+
}
|
|
43
|
+
let cachedVersion;
|
|
44
|
+
export function modwrightVersion() {
|
|
45
|
+
if (cachedVersion)
|
|
46
|
+
return cachedVersion;
|
|
47
|
+
try {
|
|
48
|
+
const raw = JSON.parse(readFileSync(path.join(packageRoot(), "package.json"), "utf8"));
|
|
49
|
+
cachedVersion = typeof raw.version === "string" ? raw.version : "0.0.0";
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
cachedVersion = "0.0.0";
|
|
53
|
+
}
|
|
54
|
+
return cachedVersion;
|
|
55
|
+
}
|
|
56
|
+
function sha256Hash(content) {
|
|
57
|
+
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
58
|
+
}
|
|
59
|
+
export function lineDiff(shipped, local, limit = 20) {
|
|
60
|
+
const a = shipped.split("\n");
|
|
61
|
+
const b = local.split("\n");
|
|
62
|
+
const width = b.length + 1;
|
|
63
|
+
const lcs = new Uint32Array((a.length + 1) * width);
|
|
64
|
+
for (let i = a.length - 1; i >= 0; i--) {
|
|
65
|
+
for (let j = b.length - 1; j >= 0; j--) {
|
|
66
|
+
lcs[i * width + j] =
|
|
67
|
+
a[i] === b[j]
|
|
68
|
+
? (lcs[(i + 1) * width + (j + 1)] ?? 0) + 1
|
|
69
|
+
: Math.max(lcs[(i + 1) * width + j] ?? 0, lcs[i * width + (j + 1)] ?? 0);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const rows = [];
|
|
73
|
+
let i = 0;
|
|
74
|
+
let j = 0;
|
|
75
|
+
while (i < a.length && j < b.length) {
|
|
76
|
+
if (a[i] === b[j]) {
|
|
77
|
+
i++;
|
|
78
|
+
j++;
|
|
79
|
+
}
|
|
80
|
+
else if ((lcs[(i + 1) * width + j] ?? 0) >= (lcs[i * width + (j + 1)] ?? 0)) {
|
|
81
|
+
rows.push({ kind: "removed", lineNumber: i + 1, text: a[i] ?? "" });
|
|
82
|
+
i++;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
rows.push({ kind: "added", lineNumber: j + 1, text: b[j] ?? "" });
|
|
86
|
+
j++;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
for (; i < a.length; i++)
|
|
90
|
+
rows.push({ kind: "removed", lineNumber: i + 1, text: a[i] ?? "" });
|
|
91
|
+
for (; j < b.length; j++)
|
|
92
|
+
rows.push({ kind: "added", lineNumber: j + 1, text: b[j] ?? "" });
|
|
93
|
+
return rows.slice(0, limit);
|
|
94
|
+
}
|
|
95
|
+
export function interpolateTemplate(content, values) {
|
|
96
|
+
return content
|
|
97
|
+
.replace(/\$\{version\}/g, values.version)
|
|
98
|
+
.replace(/\$\{game\}/g, values.game)
|
|
99
|
+
.replace(/\$\{project\}/g, values.project);
|
|
100
|
+
}
|
|
101
|
+
function normalizeForCompare(text) {
|
|
102
|
+
return text.toLowerCase().replace(/\s+/g, " ").trim();
|
|
103
|
+
}
|
|
104
|
+
export function quotedFactIds(body, facts) {
|
|
105
|
+
const haystack = normalizeForCompare(body);
|
|
106
|
+
return facts
|
|
107
|
+
.filter((fact) => {
|
|
108
|
+
const claim = normalizeForCompare(fact.claim);
|
|
109
|
+
return claim.length >= 60 && haystack.includes(claim);
|
|
110
|
+
})
|
|
111
|
+
.map((fact) => fact.id);
|
|
112
|
+
}
|
|
113
|
+
async function loadTemplate(relPath) {
|
|
114
|
+
const filePath = path.join(claudeAssetsRoot(), relPath);
|
|
115
|
+
const content = await readTextIfExists(filePath);
|
|
116
|
+
if (content === undefined) {
|
|
117
|
+
throw new Error(`claude-assets: template not found: ${relPath} (looked in ${filePath})`);
|
|
118
|
+
}
|
|
119
|
+
return content.replace(/\r\n/g, "\n");
|
|
120
|
+
}
|
|
121
|
+
export async function generateClaudeAssets(options) {
|
|
122
|
+
const { project, mode = "dry-run", force = false, includeSettings = false, skipFactCheck = false } = options;
|
|
123
|
+
const version = modwrightVersion();
|
|
124
|
+
const claudeDir = path.join(project.root, ".claude");
|
|
125
|
+
const scope = { version, game: project.project.game, project: project.project.name };
|
|
126
|
+
let facts = [];
|
|
127
|
+
if (!skipFactCheck) {
|
|
128
|
+
try {
|
|
129
|
+
facts = (await loadKnowledge()).facts.map((fact) => ({ id: fact.id, claim: fact.claim }));
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
facts = [];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const files = [];
|
|
136
|
+
const plan = { purpose: `generate Claude Code assets for ${project.project.name}`, operations: [] };
|
|
137
|
+
plan.operations.push(planMkdir(claudeDir));
|
|
138
|
+
plan.operations.push(planMkdir(path.join(claudeDir, "skills")));
|
|
139
|
+
plan.operations.push(planMkdir(path.join(claudeDir, "agents")));
|
|
140
|
+
const manifestPath = path.join(claudeDir, MANIFEST_FILE);
|
|
141
|
+
let previous;
|
|
142
|
+
const previousText = await readTextIfExists(manifestPath);
|
|
143
|
+
if (previousText) {
|
|
144
|
+
try {
|
|
145
|
+
const parsed = JSON.parse(previousText);
|
|
146
|
+
if (parsed && typeof parsed === "object" && parsed.entries)
|
|
147
|
+
previous = parsed;
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const entries = {};
|
|
153
|
+
const planAsset = async (relPath, templateId, generated) => {
|
|
154
|
+
const contentHash = sha256Hash(generated);
|
|
155
|
+
const absPath = path.join(claudeDir, relPath);
|
|
156
|
+
const recorded = previous?.entries[relPath]?.sha256;
|
|
157
|
+
const existing = await readTextIfExists(absPath);
|
|
158
|
+
let status;
|
|
159
|
+
let diff;
|
|
160
|
+
if (existing === undefined) {
|
|
161
|
+
status = "created";
|
|
162
|
+
}
|
|
163
|
+
else if (sha256Hash(existing) === contentHash) {
|
|
164
|
+
status = "unchanged";
|
|
165
|
+
}
|
|
166
|
+
else if (recorded !== undefined && sha256Hash(existing) === recorded) {
|
|
167
|
+
status = "updated";
|
|
168
|
+
}
|
|
169
|
+
else if (force) {
|
|
170
|
+
status = "overwritten";
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
status = "diverged";
|
|
174
|
+
diff = lineDiff(generated, existing);
|
|
175
|
+
}
|
|
176
|
+
if (status === "diverged") {
|
|
177
|
+
const keep = previous?.entries[relPath];
|
|
178
|
+
if (keep)
|
|
179
|
+
entries[relPath] = keep;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
entries[relPath] = { template: templateId, version, sha256: contentHash };
|
|
183
|
+
if (status !== "unchanged")
|
|
184
|
+
plan.operations.push(planFile(absPath, generated));
|
|
185
|
+
}
|
|
186
|
+
files.push({ path: relPath, template: templateId, status, sha256: contentHash, diff });
|
|
187
|
+
};
|
|
188
|
+
for (const asset of ASSETS) {
|
|
189
|
+
if (asset.games && !asset.games.includes(project.project.game))
|
|
190
|
+
continue;
|
|
191
|
+
const raw = await loadTemplate(asset.path);
|
|
192
|
+
const generated = interpolateTemplate(raw, scope);
|
|
193
|
+
const quoted = quotedFactIds(generated, facts);
|
|
194
|
+
if (quoted.length > 0) {
|
|
195
|
+
throw new Error(`claude-assets: template ${asset.path} restates knowledge fact(s) ${quoted.join(", ")} verbatim. ` +
|
|
196
|
+
`A skill body cites the fact id and calls lookup instead.`);
|
|
197
|
+
}
|
|
198
|
+
await planAsset(asset.path, asset.templateId, generated);
|
|
199
|
+
}
|
|
200
|
+
if (includeSettings) {
|
|
201
|
+
await planAsset(SETTINGS_PATH, SETTINGS_TEMPLATE_ID, `${JSON.stringify(SETTINGS_BASELINE, null, 2)}\n`);
|
|
202
|
+
}
|
|
203
|
+
const shipped = new Set([...ASSETS.map((a) => a.path), SETTINGS_PATH]);
|
|
204
|
+
const orphans = [];
|
|
205
|
+
for (const [relPath, entry] of Object.entries(previous?.entries ?? {})) {
|
|
206
|
+
if (shipped.has(relPath))
|
|
207
|
+
continue;
|
|
208
|
+
entries[relPath] = entry;
|
|
209
|
+
orphans.push({
|
|
210
|
+
path: relPath,
|
|
211
|
+
template: entry.template,
|
|
212
|
+
version: entry.version,
|
|
213
|
+
onDisk: await pathOnDisk(path.join(claudeDir, relPath)),
|
|
214
|
+
note: `ModWright ${version} no longer ships this template. Left in place — delete it yourself if it is no longer wanted.`,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
for (const [relPath, entry] of Object.entries(previous?.entries ?? {})) {
|
|
218
|
+
if (!entries[relPath])
|
|
219
|
+
entries[relPath] = entry;
|
|
220
|
+
}
|
|
221
|
+
const manifest = { schemaVersion: 1, entries };
|
|
222
|
+
plan.operations.push(planFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`));
|
|
223
|
+
const divergedCount = files.filter((file) => file.status === "diverged").length;
|
|
224
|
+
const confidence = {
|
|
225
|
+
level: "high",
|
|
226
|
+
assumptions: [
|
|
227
|
+
includeSettings
|
|
228
|
+
? ".claude/settings.json is wanted: it sets Claude Code's model and effort baseline for the whole repo."
|
|
229
|
+
: "The repo's own .claude/settings.json (if any) is left alone; each skill and agent pins its own model and effort.",
|
|
230
|
+
"A file whose sha256 no longer matches ASSETS.json was edited on purpose.",
|
|
231
|
+
],
|
|
232
|
+
verifiedBy: [
|
|
233
|
+
"every generated file hashed and compared against .claude/ASSETS.json",
|
|
234
|
+
"templates checked against knowledge/ for restated facts",
|
|
235
|
+
],
|
|
236
|
+
};
|
|
237
|
+
let writeReport;
|
|
238
|
+
let applied = false;
|
|
239
|
+
if (mode === "apply") {
|
|
240
|
+
plan.backupRoot = path.join(project.root, ".modwright", "backups");
|
|
241
|
+
writeReport = await executePlan(plan, { mode: "apply", confidence });
|
|
242
|
+
applied = writeReport.applied;
|
|
243
|
+
}
|
|
244
|
+
const next = divergedCount > 0
|
|
245
|
+
? `${divergedCount} file(s) were edited locally and were left untouched. Review each diff, then re-run with force: true to take ModWright's version (the old file is backed up first).`
|
|
246
|
+
: mode === "dry-run"
|
|
247
|
+
? 'Re-run with mode: "apply" to write these files.'
|
|
248
|
+
: undefined;
|
|
249
|
+
return {
|
|
250
|
+
mode,
|
|
251
|
+
version,
|
|
252
|
+
files,
|
|
253
|
+
orphans,
|
|
254
|
+
manifest,
|
|
255
|
+
settingsIncluded: includeSettings,
|
|
256
|
+
plan,
|
|
257
|
+
writeReport,
|
|
258
|
+
applied,
|
|
259
|
+
confidence,
|
|
260
|
+
next,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
async function pathOnDisk(absPath) {
|
|
264
|
+
try {
|
|
265
|
+
await fs.stat(absPath);
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { walk } from "../fsutil.js";
|
|
3
|
+
async function projectFilesText(project) {
|
|
4
|
+
const { promises: fs } = await import("node:fs");
|
|
5
|
+
const out = [];
|
|
6
|
+
for (const source of project.sources) {
|
|
7
|
+
if (!source.exists)
|
|
8
|
+
continue;
|
|
9
|
+
const { files } = await walk(source.absPath, { limit: 20_000 });
|
|
10
|
+
for (const rel of files) {
|
|
11
|
+
const abs = path.join(source.absPath, rel);
|
|
12
|
+
try {
|
|
13
|
+
out.push({ file: abs, text: await fs.readFile(abs, "utf8") });
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
function collisionProjectDefault(idSpec) {
|
|
22
|
+
return idSpec.collision.project ?? true;
|
|
23
|
+
}
|
|
24
|
+
export async function checkCollision(input) {
|
|
25
|
+
const { idSpec, value, project, store, install, lookup } = input;
|
|
26
|
+
const hits = [];
|
|
27
|
+
const skipped = [];
|
|
28
|
+
if (collisionProjectDefault(idSpec)) {
|
|
29
|
+
for (const { file, text } of await projectFilesText(project)) {
|
|
30
|
+
if (text.includes(value)) {
|
|
31
|
+
hits.push({ check: "project", source: file, detail: `"${value}" already appears in this project file.` });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (idSpec.collision.index) {
|
|
36
|
+
if (!store) {
|
|
37
|
+
skipped.push({ check: "index", reason: `no vanilla index built for ${project.project.game} — run build_index` });
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const { kind, param } = idSpec.collision.index;
|
|
41
|
+
const res = await lookup(kind, { [param]: value });
|
|
42
|
+
if (res.count > 0) {
|
|
43
|
+
hits.push({
|
|
44
|
+
check: "index",
|
|
45
|
+
source: `vanilla index (${kind})`,
|
|
46
|
+
detail: `"${value}" already resolves in the vanilla index (${res.count} row(s)).`,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (idSpec.collision.loadOrder) {
|
|
52
|
+
if (!install || !input.surface.loadOrder) {
|
|
53
|
+
skipped.push({ check: "loadOrder", reason: !install ? `no ${project.project.game} install located` : "this surface has no load-order reader" });
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const entries = await input.surface.loadOrder(install);
|
|
57
|
+
const match = entries.find((e) => e.uuid?.toLowerCase() === value.toLowerCase());
|
|
58
|
+
if (match) {
|
|
59
|
+
hits.push({ check: "loadOrder", source: match.name, detail: `"${value}" already appears in the load order as "${match.name}".` });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { idName: idSpec.name, value, hits, skipped };
|
|
64
|
+
}
|