@modwrench/workbench 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/data/conflicts/README.md +39 -0
  2. package/data/conflicts/fallout4.json +1 -0
  3. package/data/conflicts/lethalcompany.json +1 -0
  4. package/data/conflicts/skyrimspecialedition.json +1 -0
  5. package/dist/conflicts/community.d.ts +3 -0
  6. package/dist/conflicts/community.d.ts.map +1 -0
  7. package/dist/conflicts/community.js +96 -0
  8. package/dist/conflicts/community.js.map +1 -0
  9. package/dist/conflicts/index.d.ts +6 -0
  10. package/dist/conflicts/index.d.ts.map +1 -0
  11. package/dist/conflicts/index.js +114 -0
  12. package/dist/conflicts/index.js.map +1 -0
  13. package/dist/conflicts/loot.d.ts +19 -0
  14. package/dist/conflicts/loot.d.ts.map +1 -0
  15. package/dist/conflicts/loot.js +148 -0
  16. package/dist/conflicts/loot.js.map +1 -0
  17. package/dist/conflicts/types.d.ts +31 -0
  18. package/dist/conflicts/types.d.ts.map +1 -0
  19. package/dist/conflicts/types.js +6 -0
  20. package/dist/conflicts/types.js.map +1 -0
  21. package/dist/crashlog/bepinex.d.ts +3 -0
  22. package/dist/crashlog/bepinex.d.ts.map +1 -0
  23. package/dist/crashlog/bepinex.js +105 -0
  24. package/dist/crashlog/bepinex.js.map +1 -0
  25. package/dist/crashlog/crashlogger-sse.d.ts +3 -0
  26. package/dist/crashlog/crashlogger-sse.d.ts.map +1 -0
  27. package/dist/crashlog/crashlogger-sse.js +226 -0
  28. package/dist/crashlog/crashlogger-sse.js.map +1 -0
  29. package/dist/crashlog/detect.d.ts +3 -0
  30. package/dist/crashlog/detect.d.ts.map +1 -0
  31. package/dist/crashlog/detect.js +44 -0
  32. package/dist/crashlog/detect.js.map +1 -0
  33. package/dist/crashlog/index.d.ts +15 -0
  34. package/dist/crashlog/index.d.ts.map +1 -0
  35. package/dist/crashlog/index.js +65 -0
  36. package/dist/crashlog/index.js.map +1 -0
  37. package/dist/crashlog/minecraft.d.ts +3 -0
  38. package/dist/crashlog/minecraft.d.ts.map +1 -0
  39. package/dist/crashlog/minecraft.js +145 -0
  40. package/dist/crashlog/minecraft.js.map +1 -0
  41. package/dist/crashlog/netscriptframework.d.ts +3 -0
  42. package/dist/crashlog/netscriptframework.d.ts.map +1 -0
  43. package/dist/crashlog/netscriptframework.js +109 -0
  44. package/dist/crashlog/netscriptframework.js.map +1 -0
  45. package/dist/crashlog/types.d.ts +35 -0
  46. package/dist/crashlog/types.d.ts.map +1 -0
  47. package/dist/crashlog/types.js +9 -0
  48. package/dist/crashlog/types.js.map +1 -0
  49. package/dist/detect/environment.d.ts +35 -0
  50. package/dist/detect/environment.d.ts.map +1 -0
  51. package/dist/detect/environment.js +65 -0
  52. package/dist/detect/environment.js.map +1 -0
  53. package/dist/detect/games.d.ts +21 -0
  54. package/dist/detect/games.d.ts.map +1 -0
  55. package/dist/detect/games.js +159 -0
  56. package/dist/detect/games.js.map +1 -0
  57. package/dist/detect/loader.d.ts +12 -0
  58. package/dist/detect/loader.d.ts.map +1 -0
  59. package/dist/detect/loader.js +20 -0
  60. package/dist/detect/loader.js.map +1 -0
  61. package/dist/detect/manager.d.ts +26 -0
  62. package/dist/detect/manager.d.ts.map +1 -0
  63. package/dist/detect/manager.js +157 -0
  64. package/dist/detect/manager.js.map +1 -0
  65. package/dist/detect/os.d.ts +21 -0
  66. package/dist/detect/os.d.ts.map +1 -0
  67. package/dist/detect/os.js +58 -0
  68. package/dist/detect/os.js.map +1 -0
  69. package/dist/detect/steam.d.ts +32 -0
  70. package/dist/detect/steam.d.ts.map +1 -0
  71. package/dist/detect/steam.js +155 -0
  72. package/dist/detect/steam.js.map +1 -0
  73. package/dist/detect/vdf.d.ts +12 -0
  74. package/dist/detect/vdf.d.ts.map +1 -0
  75. package/dist/detect/vdf.js +112 -0
  76. package/dist/detect/vdf.js.map +1 -0
  77. package/dist/index.d.ts +3 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +27 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/loadorder/index.d.ts +20 -0
  82. package/dist/loadorder/index.d.ts.map +1 -0
  83. package/dist/loadorder/index.js +76 -0
  84. package/dist/loadorder/index.js.map +1 -0
  85. package/dist/loadorder/mo2.d.ts +21 -0
  86. package/dist/loadorder/mo2.d.ts.map +1 -0
  87. package/dist/loadorder/mo2.js +201 -0
  88. package/dist/loadorder/mo2.js.map +1 -0
  89. package/dist/loadorder/r2modman.d.ts +12 -0
  90. package/dist/loadorder/r2modman.d.ts.map +1 -0
  91. package/dist/loadorder/r2modman.js +115 -0
  92. package/dist/loadorder/r2modman.js.map +1 -0
  93. package/dist/loadorder/types.d.ts +30 -0
  94. package/dist/loadorder/types.d.ts.map +1 -0
  95. package/dist/loadorder/types.js +5 -0
  96. package/dist/loadorder/types.js.map +1 -0
  97. package/dist/loadorder/vortex.d.ts +8 -0
  98. package/dist/loadorder/vortex.d.ts.map +1 -0
  99. package/dist/loadorder/vortex.js +77 -0
  100. package/dist/loadorder/vortex.js.map +1 -0
  101. package/dist/metadata/clients.d.ts +11 -0
  102. package/dist/metadata/clients.d.ts.map +1 -0
  103. package/dist/metadata/clients.js +90 -0
  104. package/dist/metadata/clients.js.map +1 -0
  105. package/dist/metadata/index.d.ts +3 -0
  106. package/dist/metadata/index.d.ts.map +1 -0
  107. package/dist/metadata/index.js +149 -0
  108. package/dist/metadata/index.js.map +1 -0
  109. package/dist/metadata/normalize.d.ts +43 -0
  110. package/dist/metadata/normalize.d.ts.map +1 -0
  111. package/dist/metadata/normalize.js +88 -0
  112. package/dist/metadata/normalize.js.map +1 -0
  113. package/dist/metadata/types.d.ts +57 -0
  114. package/dist/metadata/types.d.ts.map +1 -0
  115. package/dist/metadata/types.js +9 -0
  116. package/dist/metadata/types.js.map +1 -0
  117. package/dist/register.d.ts +13 -0
  118. package/dist/register.d.ts.map +1 -0
  119. package/dist/register.js +209 -0
  120. package/dist/register.js.map +1 -0
  121. package/package.json +58 -0
@@ -0,0 +1,39 @@
1
+ # Community Conflict Database
2
+
3
+ This directory holds per-game JSON files of known mod conflicts that ModWrench's `mw_check_known_conflicts` tool reads at runtime. One file per game, named `<gameId>.json`, top-level array of conflict entries.
4
+
5
+ For Bethesda games (Skyrim SE/LE/VR, Fallout 3/NV/4, Starfield, Oblivion), the tool **also** fetches the live [LOOT masterlist](https://github.com/loot) and merges its incompatibilities. The files here are the community layer on top of LOOT — non-Bethesda games rely entirely on this layer.
6
+
7
+ ## Entry schema
8
+
9
+ ```json
10
+ {
11
+ "modA": "nexus:65876",
12
+ "modB": "nexus:35895",
13
+ "severity": "incompatible",
14
+ "description": "Both mods modify the Whiterun cell record (0x000165A8). Loading both causes a CTD on entering Whiterun. Disable one or use the linked patch.",
15
+ "source": "community",
16
+ "workaround": "JK's Compatibility Patch Hub provides a merged patch.",
17
+ "patchModId": "nexus:75512"
18
+ }
19
+ ```
20
+
21
+ ### Required fields
22
+
23
+ - `modA`, `modB` — mod identifiers. Either platform-prefixed (`nexus:N`, `modio:N`, `thunderstore:Author-ModName`) or plugin filenames (`Skyrim.esp`)
24
+ - `severity` — one of: `incompatible`, `load-order-sensitive`, `patch-available`, `informational`
25
+ - `description` — short prose explaining the conflict
26
+ - `source` — `community` for PR-submitted entries; `modwrench-curated` is reserved for entries the maintainers vouch for
27
+
28
+ ### Optional fields
29
+
30
+ - `workaround` — what to do if you must run both
31
+ - `patchModId` — a compatibility patch that resolves the conflict (same prefix conventions as modA/modB)
32
+
33
+ ## Submitting a conflict
34
+
35
+ 1. Verify the conflict is real and reproducible — cite the mod page(s) or a forum thread
36
+ 2. Open a PR adding an entry to the relevant `<gameId>.json`
37
+ 3. Use the smallest description that's still actionable for someone diagnosing a crash
38
+
39
+ ModWrench will not include speculative or unverified conflicts. If you're not sure, file an issue first.
@@ -0,0 +1 @@
1
+ []
@@ -0,0 +1 @@
1
+ []
@@ -0,0 +1,3 @@
1
+ import type { KnownConflict } from "./types.js";
2
+ export declare function loadCommunityConflicts(gameId: string): KnownConflict[];
3
+ //# sourceMappingURL=community.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"community.d.ts","sourceRoot":"","sources":["../../src/conflicts/community.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAoB,aAAa,EAAE,MAAM,YAAY,CAAC;AAsDlE,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CAuCtE"}
@@ -0,0 +1,96 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { log } from "@modwrench/core";
5
+ import { pathExists } from "../detect/os.js";
6
+ // Community-curated conflict database. One file per game, bundled with the
7
+ // workbench package, accepts PRs from contributors. Schema matches the
8
+ // wiring-prompt example:
9
+ //
10
+ // [
11
+ // {
12
+ // "modA": "nexus:65876",
13
+ // "modB": "nexus:35895",
14
+ // "severity": "incompatible",
15
+ // "description": "...",
16
+ // "source": "community",
17
+ // "workaround": "...", // optional
18
+ // "patchModId": "nexus:75512" // optional
19
+ // }
20
+ // ]
21
+ //
22
+ // modA and modB can be either platform-prefixed IDs (nexus:N, modio:N,
23
+ // thunderstore:Author-ModName) or plugin filenames (Skyrim.esp). The checker
24
+ // matches against the user's input modIds in either form.
25
+ const HERE = dirname(fileURLToPath(import.meta.url));
26
+ /**
27
+ * Locate the bundled data/conflicts directory. After build the file layout is
28
+ * dist/conflicts/community.js → ../../data/conflicts/<gameId>.json
29
+ * (workbench package root → data/conflicts/). We resolve relative to this
30
+ * module's URL so the lookup works whether the package is installed under
31
+ * node_modules or run from the workspace.
32
+ */
33
+ function dataDir() {
34
+ return resolve(HERE, "..", "..", "data", "conflicts");
35
+ }
36
+ const VALID_SEVERITIES = new Set([
37
+ "incompatible",
38
+ "load-order-sensitive",
39
+ "patch-available",
40
+ "informational",
41
+ ]);
42
+ function isValidEntry(raw) {
43
+ if (!raw || typeof raw !== "object")
44
+ return false;
45
+ const e = raw;
46
+ return (typeof e["modA"] === "string" &&
47
+ typeof e["modB"] === "string" &&
48
+ typeof e["severity"] === "string" &&
49
+ VALID_SEVERITIES.has(e["severity"]) &&
50
+ typeof e["description"] === "string");
51
+ }
52
+ export function loadCommunityConflicts(gameId) {
53
+ const path = resolve(dataDir(), `${gameId}.json`);
54
+ if (!pathExists(path))
55
+ return [];
56
+ let parsed;
57
+ try {
58
+ parsed = JSON.parse(readFileSync(path, "utf8"));
59
+ }
60
+ catch (err) {
61
+ log("warn", "workbench.community.parse_error", {
62
+ gameId,
63
+ msg: err instanceof Error ? err.message : String(err),
64
+ });
65
+ return [];
66
+ }
67
+ if (!Array.isArray(parsed)) {
68
+ log("warn", "workbench.community.shape_error", {
69
+ gameId,
70
+ msg: "expected top-level array",
71
+ });
72
+ return [];
73
+ }
74
+ const out = [];
75
+ for (const raw of parsed) {
76
+ if (!isValidEntry(raw))
77
+ continue;
78
+ // Force the source field to "community" if it claims something stronger.
79
+ // modwrench-curated is reserved for entries we vouch for; community-PR
80
+ // entries default to "community" until promoted.
81
+ const entry = {
82
+ modA: raw.modA,
83
+ modB: raw.modB,
84
+ severity: raw.severity,
85
+ description: raw.description,
86
+ source: raw.source === "modwrench-curated" ? "modwrench-curated" : "community",
87
+ };
88
+ if (raw.workaround)
89
+ entry.workaround = raw.workaround;
90
+ if (raw.patchModId)
91
+ entry.patchModId = raw.patchModId;
92
+ out.push(entry);
93
+ }
94
+ return out;
95
+ }
96
+ //# sourceMappingURL=community.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"community.js","sourceRoot":"","sources":["../../src/conflicts/community.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { CheckKnownConflictsInput, CheckKnownConflictsResult, KnownConflict } from "./types.js";
2
+ export declare function looksLikePlugin(id: string): boolean;
3
+ export declare function normalizeId(id: string): string;
4
+ export declare function findCommunityConflicts(entries: KnownConflict[], inputSet: Set<string>): KnownConflict[];
5
+ export declare function checkKnownConflicts(input: CheckKnownConflictsInput): Promise<CheckKnownConflictsResult>;
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/conflicts/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,wBAAwB,EACxB,yBAAyB,EACzB,aAAa,EACd,MAAM,YAAY,CAAC;AAapB,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE9C;AA2CD,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,aAAa,EAAE,EACxB,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,GACpB,aAAa,EAAE,CAQjB;AAED,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,wBAAwB,GAC9B,OAAO,CAAC,yBAAyB,CAAC,CAoDpC"}
@@ -0,0 +1,114 @@
1
+ import { fetchLootMasterlist, hasLootSupport, } from "./loot.js";
2
+ import { loadCommunityConflicts } from "./community.js";
3
+ // Identifier normalization. Input modIds can be:
4
+ // - Plugin filename: "Skyrim.esp", "JK's Whiterun Outskirts.esp"
5
+ // - Platform mod ID: "nexus:12345", "modio:67890",
6
+ // "thunderstore:Author-ModName"
7
+ // - Bare numeric (assumed nexus): "12345" → we don't auto-prefix; only
8
+ // explicit prefixes match
9
+ //
10
+ // Matching:
11
+ // - Plugin names are case-insensitive (LOOT convention).
12
+ // - Platform IDs are case-sensitive on the suffix, lowercase on the prefix.
13
+ export function looksLikePlugin(id) {
14
+ return /\.(esp|esm|esl)$/i.test(id);
15
+ }
16
+ export function normalizeId(id) {
17
+ return looksLikePlugin(id) ? id.toLowerCase() : id;
18
+ }
19
+ function dedupePairs(conflicts) {
20
+ const seen = new Set();
21
+ const out = [];
22
+ for (const c of conflicts) {
23
+ const a = normalizeId(c.modA);
24
+ const b = normalizeId(c.modB);
25
+ const key = [a, b].sort().join("||") + "::" + c.source;
26
+ if (seen.has(key))
27
+ continue;
28
+ seen.add(key);
29
+ out.push(c);
30
+ }
31
+ return out;
32
+ }
33
+ function findLootConflicts(masterlist, inputSet, pluginInputs) {
34
+ const conflicts = [];
35
+ for (const plugin of pluginInputs) {
36
+ const rule = masterlist.plugins.get(plugin.toLowerCase());
37
+ if (!rule)
38
+ continue;
39
+ for (const inc of rule.inc) {
40
+ const incLower = inc.plugin.toLowerCase();
41
+ if (!inputSet.has(incLower))
42
+ continue;
43
+ const entry = {
44
+ modA: rule.name,
45
+ modB: inc.plugin,
46
+ severity: "incompatible",
47
+ description: inc.msg ??
48
+ `LOOT masterlist flags ${rule.name} and ${inc.plugin} as incompatible.`,
49
+ source: "loot-masterlist",
50
+ };
51
+ conflicts.push(entry);
52
+ }
53
+ }
54
+ return conflicts;
55
+ }
56
+ export function findCommunityConflicts(entries, inputSet) {
57
+ const matches = [];
58
+ for (const entry of entries) {
59
+ const a = normalizeId(entry.modA);
60
+ const b = normalizeId(entry.modB);
61
+ if (inputSet.has(a) && inputSet.has(b))
62
+ matches.push(entry);
63
+ }
64
+ return matches;
65
+ }
66
+ export async function checkKnownConflicts(input) {
67
+ const inputSet = new Set(input.modIds.map(normalizeId));
68
+ const pluginInputs = input.modIds.filter(looksLikePlugin);
69
+ const warnings = [];
70
+ // ─── LOOT pass ──────────────────────────────────────────────────────────────
71
+ const lootStatus = {
72
+ available: false,
73
+ };
74
+ let lootConflicts = [];
75
+ if (!hasLootSupport(input.gameId)) {
76
+ lootStatus.reason = `LOOT masterlists exist only for Bethesda games. ${input.gameId} has no LOOT repo.`;
77
+ }
78
+ else if (pluginInputs.length === 0) {
79
+ lootStatus.reason =
80
+ "LOOT pass skipped — no plugin filenames in input. " +
81
+ "Pass plugin filenames (.esp/.esm/.esl) to engage LOOT checks.";
82
+ }
83
+ else {
84
+ const masterlist = await fetchLootMasterlist(input.gameId);
85
+ if (!masterlist) {
86
+ lootStatus.reason =
87
+ "LOOT masterlist fetch failed (network, GitHub unreachable, or " +
88
+ "parse error). Conflict detection falls back to community data only.";
89
+ warnings.push(lootStatus.reason);
90
+ }
91
+ else {
92
+ lootStatus.available = true;
93
+ lootConflicts = findLootConflicts(masterlist, inputSet, pluginInputs);
94
+ }
95
+ }
96
+ // ─── Community pass ─────────────────────────────────────────────────────────
97
+ const communityEntries = loadCommunityConflicts(input.gameId);
98
+ const communityConflicts = findCommunityConflicts(communityEntries, inputSet);
99
+ const conflicts = dedupePairs([...lootConflicts, ...communityConflicts]);
100
+ const result = {
101
+ conflicts,
102
+ sources: {
103
+ loot: lootStatus,
104
+ community: {
105
+ available: true,
106
+ entries: communityEntries.length,
107
+ },
108
+ },
109
+ };
110
+ if (warnings.length > 0)
111
+ result.warnings = warnings;
112
+ return result;
113
+ }
114
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/conflicts/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export type LootIncEntry = {
2
+ plugin: string;
3
+ /** Optional LOOT message (often clarifies why these conflict). */
4
+ msg?: string;
5
+ };
6
+ export type LootPluginRule = {
7
+ name: string;
8
+ inc: LootIncEntry[];
9
+ };
10
+ export type LootMasterlist = {
11
+ gameId: string;
12
+ branch: string;
13
+ fetchedAt: number;
14
+ /** Map keyed by lowercased plugin name → rule. LOOT names are case-insensitive. */
15
+ plugins: Map<string, LootPluginRule>;
16
+ };
17
+ export declare function fetchLootMasterlist(gameId: string): Promise<LootMasterlist | null>;
18
+ export declare function hasLootSupport(gameId: string): boolean;
19
+ //# sourceMappingURL=loot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loot.d.ts","sourceRoot":"","sources":["../../src/conflicts/loot.ts"],"names":[],"mappings":"AA0CA,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,YAAY,EAAE,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,mFAAmF;IACnF,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CACtC,CAAC;AAgEF,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CA0ChC;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEtD"}
@@ -0,0 +1,148 @@
1
+ import { load as yamlLoad } from "js-yaml";
2
+ import { getEnv, log } from "@modwrench/core";
3
+ // LOOT publishes a separate masterlist repo per game. Each repo's
4
+ // masterlist.yaml is the community-curated source of truth for load-order
5
+ // rules and known incompatibilities. We fetch the raw file from GitHub —
6
+ // no auth needed.
7
+ //
8
+ // The schema we care about (LOOT spec v0.21+):
9
+ // plugins:
10
+ // - name: 'SomeMod.esp'
11
+ // inc:
12
+ // - 'IncompatibleMod.esp'
13
+ // - name: 'ConditionalInc.esp'
14
+ // condition: 'file("...")' # ignored — we don't evaluate
15
+ // msg: 'A note about when this conflicts'
16
+ //
17
+ // Other LOOT fields (after, req, msg, group, etc.) aren't used by this tool
18
+ // — they're for load-order optimization, not incompatibility detection.
19
+ // Pinned to a stable LOOT version branch rather than `master` so an upstream
20
+ // schema change can't silently break our parser. v0.26 is the current
21
+ // maintenance branch as of 2026 — community conflict entries keep flowing
22
+ // into it via PRs while the schema stays frozen until v0.27.
23
+ //
24
+ // Bump this default deliberately after verifying the masterlist YAML still
25
+ // parses against parseLootMasterlist below. Users can override via env var.
26
+ const LOOT_BRANCH = getEnv("LOOT_BRANCH", "v0.26");
27
+ // Per-game LOOT repo names. Only Bethesda games have LOOT masterlists.
28
+ const LOOT_REPOS = {
29
+ skyrimspecialedition: "skyrimse",
30
+ skyrim: "skyrim",
31
+ skyrimvr: "skyrimvr",
32
+ fallout4: "fallout4",
33
+ fallout4vr: "fallout4vr",
34
+ fallout3: "fallout3",
35
+ falloutnv: "falloutnv",
36
+ starfield: "starfield",
37
+ oblivion: "oblivion",
38
+ };
39
+ const memCache = new Map();
40
+ function normalizeIncEntry(raw) {
41
+ if (typeof raw === "string")
42
+ return { plugin: raw };
43
+ if (raw && typeof raw === "object") {
44
+ const obj = raw;
45
+ const name = obj["name"];
46
+ if (typeof name === "string") {
47
+ const entry = { plugin: name };
48
+ const msg = obj["msg"];
49
+ if (typeof msg === "string")
50
+ entry.msg = msg;
51
+ return entry;
52
+ }
53
+ }
54
+ return null;
55
+ }
56
+ function parseLootMasterlist(text, gameId, branch) {
57
+ let doc;
58
+ try {
59
+ doc = yamlLoad(text);
60
+ }
61
+ catch (err) {
62
+ log("warn", "workbench.loot.parse_error", {
63
+ gameId,
64
+ msg: err instanceof Error ? err.message : String(err),
65
+ });
66
+ return null;
67
+ }
68
+ if (!doc || typeof doc !== "object")
69
+ return null;
70
+ const plugins = doc["plugins"];
71
+ if (!Array.isArray(plugins))
72
+ return null;
73
+ const map = new Map();
74
+ for (const raw of plugins) {
75
+ if (!raw || typeof raw !== "object")
76
+ continue;
77
+ const entry = raw;
78
+ const name = entry["name"];
79
+ if (typeof name !== "string")
80
+ continue;
81
+ const incRaw = entry["inc"];
82
+ const inc = [];
83
+ if (Array.isArray(incRaw)) {
84
+ for (const i of incRaw) {
85
+ const normalized = normalizeIncEntry(i);
86
+ if (normalized)
87
+ inc.push(normalized);
88
+ }
89
+ }
90
+ if (inc.length === 0)
91
+ continue;
92
+ map.set(name.toLowerCase(), { name, inc });
93
+ }
94
+ return {
95
+ gameId,
96
+ branch,
97
+ fetchedAt: Date.now(),
98
+ plugins: map,
99
+ };
100
+ }
101
+ export async function fetchLootMasterlist(gameId) {
102
+ const repo = LOOT_REPOS[gameId];
103
+ if (!repo)
104
+ return null;
105
+ const cacheKey = `${gameId}:${LOOT_BRANCH}`;
106
+ const cached = memCache.get(cacheKey);
107
+ if (cached)
108
+ return cached instanceof Promise ? cached : cached;
109
+ const url = `https://raw.githubusercontent.com/loot/${repo}/${LOOT_BRANCH}/masterlist.yaml`;
110
+ log("debug", "workbench.loot.fetch", { url });
111
+ const pending = (async () => {
112
+ try {
113
+ const res = await fetch(url, {
114
+ headers: {
115
+ Accept: "text/yaml, text/plain;q=0.9, */*;q=0.5",
116
+ "User-Agent": "ModWrench/0.0.1 (+https://github.com/171county/modwrench)",
117
+ },
118
+ });
119
+ if (!res.ok) {
120
+ log("warn", "workbench.loot.fetch_failed", {
121
+ url,
122
+ status: res.status,
123
+ });
124
+ return null;
125
+ }
126
+ const text = await res.text();
127
+ return parseLootMasterlist(text, gameId, LOOT_BRANCH);
128
+ }
129
+ catch (err) {
130
+ log("warn", "workbench.loot.fetch_error", {
131
+ url,
132
+ msg: err instanceof Error ? err.message : String(err),
133
+ });
134
+ return null;
135
+ }
136
+ })();
137
+ memCache.set(cacheKey, pending);
138
+ const resolved = await pending;
139
+ if (resolved)
140
+ memCache.set(cacheKey, resolved);
141
+ else
142
+ memCache.delete(cacheKey);
143
+ return resolved;
144
+ }
145
+ export function hasLootSupport(gameId) {
146
+ return gameId in LOOT_REPOS;
147
+ }
148
+ //# sourceMappingURL=loot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loot.js","sourceRoot":"","sources":["../../src/conflicts/loot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ export type ConflictSeverity = "incompatible" | "load-order-sensitive" | "patch-available" | "informational";
2
+ export type ConflictSource = "loot-masterlist" | "community" | "modwrench-curated";
3
+ export type KnownConflict = {
4
+ modA: string;
5
+ modB: string;
6
+ severity: ConflictSeverity;
7
+ description: string;
8
+ source: ConflictSource;
9
+ workaround?: string;
10
+ patchModId?: string;
11
+ };
12
+ export type CheckKnownConflictsInput = {
13
+ gameId: string;
14
+ modIds: string[];
15
+ };
16
+ export type CheckKnownConflictsResult = {
17
+ conflicts: KnownConflict[];
18
+ sources: {
19
+ loot: {
20
+ available: boolean;
21
+ reason?: string;
22
+ };
23
+ community: {
24
+ available: boolean;
25
+ entries: number;
26
+ };
27
+ };
28
+ /** Honest about gaps — surfaces partial-data warnings to the LLM. */
29
+ warnings?: string[];
30
+ };
31
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/conflicts/types.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,gBAAgB,GACxB,cAAc,GACd,sBAAsB,GACtB,iBAAiB,GACjB,eAAe,CAAC;AAEpB,MAAM,MAAM,cAAc,GACtB,iBAAiB,GACjB,WAAW,GACX,mBAAmB,CAAC;AAExB,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,cAAc,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,OAAO,EAAE;QACP,IAAI,EAAE;YAAE,SAAS,EAAE,OAAO,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAC9C,SAAS,EAAE;YAAE,SAAS,EAAE,OAAO,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC;KACpD,CAAC;IACF,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB,CAAC"}
@@ -0,0 +1,6 @@
1
+ // Shared types for known-conflict detection. Two sources feed this:
2
+ // 1. LOOT masterlist (live fetch from GitHub, Bethesda games only)
3
+ // 2. data/conflicts/<gameId>.json (bundled with the workbench package, any
4
+ // game — accepts community PRs)
5
+ export {};
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/conflicts/types.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,qEAAqE;AACrE,6EAA6E;AAC7E,qCAAqC"}
@@ -0,0 +1,3 @@
1
+ import type { CrashlogParseResult } from "./types.js";
2
+ export declare function parseBepInExLog(text: string): CrashlogParseResult;
3
+ //# sourceMappingURL=bepinex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bepinex.d.ts","sourceRoot":"","sources":["../../src/crashlog/bepinex.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,mBAAmB,EAEpB,MAAM,YAAY,CAAC;AAiEpB,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,CAsDjE"}
@@ -0,0 +1,105 @@
1
+ // BepInEx LogOutput.log is a streaming log, not a crash dump. We pull the
2
+ // fatal/error events with their stack traces and the loaded plugin list that
3
+ // BepInEx prints at startup. Multiple crashes can appear in one file; we
4
+ // report all of them and let the LLM decide which is current.
5
+ //
6
+ // Typical event header:
7
+ // [Fatal : Unity Log] NullReferenceException: Object reference not set...
8
+ // [Error : SomePlugin] Stack trace follows on subsequent lines indented...
9
+ //
10
+ // Plugin-loaded lines look like:
11
+ // [Info : BepInEx] Loading [SomePlugin 1.2.3]
12
+ const HEADER_REGEX = /^\[(Message|Info|Warning|Error|Fatal|Debug)\s*:\s*([^\]]+)\]\s*(.*)$/;
13
+ const PLUGIN_LOAD_REGEX = /Loading \[([^\]]+?)(?:\s+([\d.]+))?\]\s*$/;
14
+ const STACK_FRAME_REGEX = /^\s*at\s+(.+?)(?:\s+\(.*\))?\s*(?:in\s+(.+))?$/;
15
+ function parseEvents(text) {
16
+ const lines = text.split(/\r?\n/);
17
+ const events = [];
18
+ let current = null;
19
+ for (const line of lines) {
20
+ const header = line.match(HEADER_REGEX);
21
+ if (header) {
22
+ if (current)
23
+ events.push(current);
24
+ current = {
25
+ level: header[1] ?? "Info",
26
+ source: (header[2] ?? "").trim(),
27
+ message: header[3] ?? "",
28
+ stackLines: [],
29
+ };
30
+ continue;
31
+ }
32
+ if (current && line.trim()) {
33
+ current.stackLines.push(line);
34
+ }
35
+ }
36
+ if (current)
37
+ events.push(current);
38
+ return events;
39
+ }
40
+ function frameFromStackLine(line) {
41
+ const match = line.match(STACK_FRAME_REGEX);
42
+ if (!match)
43
+ return null;
44
+ const symbol = (match[1] ?? "").trim();
45
+ if (!symbol)
46
+ return null;
47
+ // C# format: Namespace.Class.Method(args) — module is the namespace root.
48
+ const moduleMatch = symbol.match(/^([^.\s]+)\./);
49
+ const frame = {
50
+ module: moduleMatch?.[1] ?? "(unknown)",
51
+ function: symbol,
52
+ };
53
+ if (match[2])
54
+ frame.offset = match[2].trim();
55
+ return frame;
56
+ }
57
+ export function parseBepInExLog(text) {
58
+ const events = parseEvents(text);
59
+ const loadedPlugins = [];
60
+ const fatalEvents = [];
61
+ for (const ev of events) {
62
+ if (ev.level === "Fatal" || ev.level === "Error") {
63
+ fatalEvents.push(ev);
64
+ }
65
+ const pluginMatch = ev.message.match(PLUGIN_LOAD_REGEX);
66
+ if (pluginMatch) {
67
+ const entry = { name: pluginMatch[1] ?? "(unnamed)" };
68
+ // Don't have an index for BepInEx plugins — they load in dependency
69
+ // order, not by numeric slot.
70
+ loadedPlugins.push(entry);
71
+ }
72
+ }
73
+ // Surface the most recent fatal event as the exception; older ones go into
74
+ // rawSections so the LLM can still see them if it asks.
75
+ const primary = fatalEvents[fatalEvents.length - 1];
76
+ const callStack = [];
77
+ if (primary) {
78
+ for (const stackLine of primary.stackLines) {
79
+ const frame = frameFromStackLine(stackLine);
80
+ if (frame)
81
+ callStack.push(frame);
82
+ }
83
+ }
84
+ const exception = primary
85
+ ? {
86
+ type: (primary.message.match(/^([A-Za-z.]+Exception)/) ?? [])[1],
87
+ description: `${primary.level}:${primary.source}: ${primary.message}`,
88
+ }
89
+ : {};
90
+ const rawSections = {};
91
+ if (fatalEvents.length > 1) {
92
+ rawSections["earlier_fatal_events"] = fatalEvents
93
+ .slice(0, -1)
94
+ .map((e) => `[${e.level}:${e.source}] ${e.message}\n${e.stackLines.join("\n")}`)
95
+ .join("\n\n");
96
+ }
97
+ return {
98
+ detectedType: "bepinex",
99
+ exception,
100
+ callStack,
101
+ loadedPlugins,
102
+ rawSections,
103
+ };
104
+ }
105
+ //# sourceMappingURL=bepinex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bepinex.js","sourceRoot":"","sources":["../../src/crashlog/bepinex.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { CrashlogParseResult, CrashlogType } from "./types.js";
2
+ export declare function parseCrashloggerSse(text: string, variant: Extract<CrashlogType, "crashlogger-sse" | "buffout4">): CrashlogParseResult;
3
+ //# sourceMappingURL=crashlogger-sse.d.ts.map