@adhisang/minecraft-modding-mcp 3.1.1 → 4.0.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/CHANGELOG.md +49 -0
- package/README.md +37 -18
- package/dist/access-transformer-parser.d.ts +17 -0
- package/dist/access-transformer-parser.js +97 -0
- package/dist/cache-registry.d.ts +1 -1
- package/dist/cache-registry.js +10 -2
- package/dist/concurrency.d.ts +1 -0
- package/dist/concurrency.js +24 -0
- package/dist/config.d.ts +10 -1
- package/dist/config.js +52 -1
- package/dist/decompiler/vineflower.js +22 -21
- package/dist/entry-tools/analyze-mod-service.d.ts +4 -4
- package/dist/entry-tools/analyze-symbol-service.d.ts +22 -22
- package/dist/entry-tools/analyze-symbol-service.js +13 -2
- package/dist/entry-tools/inspect-minecraft-service.d.ts +168 -168
- package/dist/entry-tools/inspect-minecraft-service.js +8 -2
- package/dist/entry-tools/manage-cache-service.d.ts +4 -4
- package/dist/entry-tools/validate-project-service.d.ts +153 -16
- package/dist/entry-tools/validate-project-service.js +442 -25
- package/dist/gradle-paths.d.ts +4 -0
- package/dist/gradle-paths.js +57 -0
- package/dist/index.js +148 -30
- package/dist/lru-list.d.ts +31 -0
- package/dist/lru-list.js +102 -0
- package/dist/mapping-pipeline-service.d.ts +12 -1
- package/dist/mapping-pipeline-service.js +28 -1
- package/dist/mapping-service.d.ts +16 -0
- package/dist/mapping-service.js +405 -68
- package/dist/minecraft-explorer-service.d.ts +13 -0
- package/dist/minecraft-explorer-service.js +8 -4
- package/dist/mixin-validator.d.ts +33 -2
- package/dist/mixin-validator.js +218 -17
- package/dist/mod-analyzer.d.ts +1 -0
- package/dist/mod-analyzer.js +17 -1
- package/dist/mod-decompile-service.js +4 -4
- package/dist/mod-remap-service.js +1 -54
- package/dist/mod-search-service.d.ts +1 -0
- package/dist/mod-search-service.js +84 -51
- package/dist/observability.d.ts +18 -1
- package/dist/observability.js +44 -1
- package/dist/response-utils.d.ts +69 -0
- package/dist/response-utils.js +227 -0
- package/dist/source-jar-reader.d.ts +16 -0
- package/dist/source-jar-reader.js +103 -1
- package/dist/source-resolver.d.ts +9 -1
- package/dist/source-resolver.js +23 -16
- package/dist/source-service.d.ts +119 -3
- package/dist/source-service.js +1836 -218
- package/dist/storage/artifacts-repo.d.ts +4 -1
- package/dist/storage/artifacts-repo.js +33 -5
- package/dist/storage/files-repo.d.ts +0 -2
- package/dist/storage/files-repo.js +0 -11
- package/dist/storage/migrations.d.ts +1 -1
- package/dist/storage/migrations.js +10 -2
- package/dist/storage/schema.d.ts +2 -0
- package/dist/storage/schema.js +25 -0
- package/dist/tool-contract-manifest.js +8 -6
- package/dist/types.d.ts +20 -0
- package/dist/workspace-mapping-service.d.ts +13 -0
- package/dist/workspace-mapping-service.js +146 -14
- package/package.json +3 -1
|
@@ -3,6 +3,7 @@ import type { ArtifactProvenance, ArtifactRow, SourceMapping, SourceOrigin } fro
|
|
|
3
3
|
type SqliteDatabase = InstanceType<typeof Database>;
|
|
4
4
|
interface UpsertArtifactInput {
|
|
5
5
|
artifactId: string;
|
|
6
|
+
alias?: string;
|
|
6
7
|
origin: SourceOrigin;
|
|
7
8
|
coordinate?: string;
|
|
8
9
|
version?: string;
|
|
@@ -27,6 +28,7 @@ export declare class ArtifactsRepo {
|
|
|
27
28
|
private readonly upsertStmt;
|
|
28
29
|
private readonly getStmt;
|
|
29
30
|
private readonly touchStmt;
|
|
31
|
+
private readonly setAliasStmt;
|
|
30
32
|
private readonly deleteStmt;
|
|
31
33
|
private readonly listStmt;
|
|
32
34
|
private readonly countStmt;
|
|
@@ -34,8 +36,9 @@ export declare class ArtifactsRepo {
|
|
|
34
36
|
private readonly listLruWithContentBytesStmt;
|
|
35
37
|
constructor(db: SqliteDatabase);
|
|
36
38
|
upsertArtifact(input: UpsertArtifactInput): void;
|
|
37
|
-
getArtifact(
|
|
39
|
+
getArtifact(artifactIdOrAlias: string): ArtifactRow | undefined;
|
|
38
40
|
touchArtifact(artifactId: string, timestamp: string): void;
|
|
41
|
+
setAlias(artifactId: string, alias: string): void;
|
|
39
42
|
deleteArtifact(artifactId: string): void;
|
|
40
43
|
listArtifactsByLru(limit: number): ArtifactRow[];
|
|
41
44
|
countArtifacts(): number;
|
|
@@ -35,6 +35,7 @@ function parseQualityFlags(value) {
|
|
|
35
35
|
function toArtifactRow(record) {
|
|
36
36
|
return {
|
|
37
37
|
artifactId: record.artifact_id,
|
|
38
|
+
alias: record.alias ?? undefined,
|
|
38
39
|
origin: record.origin,
|
|
39
40
|
coordinate: record.coordinate ?? undefined,
|
|
40
41
|
version: record.version ?? undefined,
|
|
@@ -59,6 +60,7 @@ export class ArtifactsRepo {
|
|
|
59
60
|
upsertStmt;
|
|
60
61
|
getStmt;
|
|
61
62
|
touchStmt;
|
|
63
|
+
setAliasStmt;
|
|
62
64
|
deleteStmt;
|
|
63
65
|
listStmt;
|
|
64
66
|
countStmt;
|
|
@@ -68,11 +70,12 @@ export class ArtifactsRepo {
|
|
|
68
70
|
this.db = db;
|
|
69
71
|
this.upsertStmt = this.db.prepare(`
|
|
70
72
|
INSERT INTO artifacts (
|
|
71
|
-
artifact_id, origin, coordinate, version, binary_jar_path, source_jar_path, repo_url, requested_mapping, mapping_applied, provenance_json, quality_flags_json, artifact_signature, is_decompiled, created_at, updated_at
|
|
73
|
+
artifact_id, alias, origin, coordinate, version, binary_jar_path, source_jar_path, repo_url, requested_mapping, mapping_applied, provenance_json, quality_flags_json, artifact_signature, is_decompiled, created_at, updated_at
|
|
72
74
|
) VALUES (
|
|
73
|
-
@artifact_id, @origin, @coordinate, @version, @binary_jar_path, @source_jar_path, @repo_url, @requested_mapping, @mapping_applied, @provenance_json, @quality_flags_json, @artifact_signature, @is_decompiled, @created_at, @updated_at
|
|
75
|
+
@artifact_id, @alias, @origin, @coordinate, @version, @binary_jar_path, @source_jar_path, @repo_url, @requested_mapping, @mapping_applied, @provenance_json, @quality_flags_json, @artifact_signature, @is_decompiled, @created_at, @updated_at
|
|
74
76
|
)
|
|
75
77
|
ON CONFLICT(artifact_id) DO UPDATE SET
|
|
78
|
+
alias = excluded.alias,
|
|
76
79
|
origin = excluded.origin,
|
|
77
80
|
coordinate = excluded.coordinate,
|
|
78
81
|
version = excluded.version,
|
|
@@ -87,9 +90,12 @@ export class ArtifactsRepo {
|
|
|
87
90
|
is_decompiled = excluded.is_decompiled,
|
|
88
91
|
updated_at = excluded.updated_at
|
|
89
92
|
`);
|
|
93
|
+
// artifact_id is a 64-char SHA hex; alias is `<type>-<...>-<6charhex>` containing
|
|
94
|
+
// dashes/letters. The two namespaces cannot collide, so OR-matching is unambiguous.
|
|
90
95
|
this.getStmt = this.db.prepare(`
|
|
91
96
|
SELECT
|
|
92
97
|
artifact_id,
|
|
98
|
+
alias,
|
|
93
99
|
origin,
|
|
94
100
|
coordinate,
|
|
95
101
|
version,
|
|
@@ -105,17 +111,28 @@ export class ArtifactsRepo {
|
|
|
105
111
|
created_at,
|
|
106
112
|
updated_at
|
|
107
113
|
FROM artifacts
|
|
108
|
-
WHERE artifact_id = ?
|
|
114
|
+
WHERE artifact_id = ? OR alias = ?
|
|
115
|
+
LIMIT 1
|
|
109
116
|
`);
|
|
110
117
|
this.touchStmt = this.db.prepare(`
|
|
111
118
|
UPDATE artifacts
|
|
112
119
|
SET updated_at = ?
|
|
113
120
|
WHERE artifact_id = ?
|
|
121
|
+
`);
|
|
122
|
+
// Persists alias on cache-hit / migrated-row paths where upsertArtifact would
|
|
123
|
+
// otherwise be skipped. Conditional WHERE keeps it idempotent and avoids a
|
|
124
|
+
// pointless write when alias is already correct.
|
|
125
|
+
this.setAliasStmt = this.db.prepare(`
|
|
126
|
+
UPDATE artifacts
|
|
127
|
+
SET alias = ?
|
|
128
|
+
WHERE artifact_id = ?
|
|
129
|
+
AND (alias IS NULL OR alias <> ?)
|
|
114
130
|
`);
|
|
115
131
|
this.deleteStmt = this.db.prepare(`DELETE FROM artifacts WHERE artifact_id = ?`);
|
|
116
132
|
this.listStmt = this.db.prepare(`
|
|
117
133
|
SELECT
|
|
118
134
|
artifact_id,
|
|
135
|
+
alias,
|
|
119
136
|
origin,
|
|
120
137
|
coordinate,
|
|
121
138
|
version,
|
|
@@ -157,6 +174,7 @@ export class ArtifactsRepo {
|
|
|
157
174
|
upsertArtifact(input) {
|
|
158
175
|
this.upsertStmt.run({
|
|
159
176
|
artifact_id: input.artifactId,
|
|
177
|
+
alias: input.alias ?? null,
|
|
160
178
|
origin: input.origin,
|
|
161
179
|
coordinate: input.coordinate ?? null,
|
|
162
180
|
version: input.version ?? null,
|
|
@@ -173,8 +191,11 @@ export class ArtifactsRepo {
|
|
|
173
191
|
updated_at: input.timestamp
|
|
174
192
|
});
|
|
175
193
|
}
|
|
176
|
-
|
|
177
|
-
|
|
194
|
+
// Accepts either an artifact_id (64-char SHA hex) or a human-readable alias
|
|
195
|
+
// (e.g. `mc-1.21.10-mojang-merged-5ad2e7`). The two key namespaces cannot
|
|
196
|
+
// collide because aliases always contain `-` and a non-hex prefix.
|
|
197
|
+
getArtifact(artifactIdOrAlias) {
|
|
198
|
+
const row = this.getStmt.get([artifactIdOrAlias, artifactIdOrAlias]);
|
|
178
199
|
if (!row) {
|
|
179
200
|
return undefined;
|
|
180
201
|
}
|
|
@@ -183,6 +204,13 @@ export class ArtifactsRepo {
|
|
|
183
204
|
touchArtifact(artifactId, timestamp) {
|
|
184
205
|
this.touchStmt.run([timestamp, artifactId]);
|
|
185
206
|
}
|
|
207
|
+
// Backfills or rotates the alias for an existing row. Used by warm-cache
|
|
208
|
+
// resolveArtifact paths where upsertArtifact is skipped, so a freshly
|
|
209
|
+
// computed alias still reaches the DB and stays in sync with the value the
|
|
210
|
+
// caller just received in the response.
|
|
211
|
+
setAlias(artifactId, alias) {
|
|
212
|
+
this.setAliasStmt.run([alias, artifactId, alias]);
|
|
213
|
+
}
|
|
186
214
|
deleteArtifact(artifactId) {
|
|
187
215
|
this.deleteStmt.run([artifactId]);
|
|
188
216
|
}
|
|
@@ -43,8 +43,6 @@ export declare class FilesRepo {
|
|
|
43
43
|
private readonly db;
|
|
44
44
|
private readonly deleteStmt;
|
|
45
45
|
private readonly insertFilesStmt;
|
|
46
|
-
private readonly insertFtsStmt;
|
|
47
|
-
private readonly deleteFtsStmt;
|
|
48
46
|
private readonly getContentStmt;
|
|
49
47
|
private readonly listStmt;
|
|
50
48
|
private readonly listRowsStmt;
|
|
@@ -125,8 +125,6 @@ export class FilesRepo {
|
|
|
125
125
|
db;
|
|
126
126
|
deleteStmt;
|
|
127
127
|
insertFilesStmt;
|
|
128
|
-
insertFtsStmt;
|
|
129
|
-
deleteFtsStmt;
|
|
130
128
|
getContentStmt;
|
|
131
129
|
listStmt;
|
|
132
130
|
listRowsStmt;
|
|
@@ -138,17 +136,10 @@ export class FilesRepo {
|
|
|
138
136
|
this.db = db;
|
|
139
137
|
this.deleteStmt = this.db.prepare(`
|
|
140
138
|
DELETE FROM files WHERE artifact_id = ?
|
|
141
|
-
`);
|
|
142
|
-
this.deleteFtsStmt = this.db.prepare(`
|
|
143
|
-
DELETE FROM files_fts WHERE artifact_id = ?
|
|
144
139
|
`);
|
|
145
140
|
this.insertFilesStmt = this.db.prepare(`
|
|
146
141
|
INSERT INTO files (artifact_id, file_path, content, content_bytes, content_hash)
|
|
147
142
|
VALUES (?, ?, ?, ?, ?)
|
|
148
|
-
`);
|
|
149
|
-
this.insertFtsStmt = this.db.prepare(`
|
|
150
|
-
INSERT INTO files_fts (artifact_id, file_path, content)
|
|
151
|
-
VALUES (?, ?, ?)
|
|
152
143
|
`);
|
|
153
144
|
this.getContentStmt = this.db.prepare(`
|
|
154
145
|
SELECT artifact_id, file_path, content, content_bytes, content_hash
|
|
@@ -187,7 +178,6 @@ export class FilesRepo {
|
|
|
187
178
|
}
|
|
188
179
|
clearFilesForArtifact(artifactId) {
|
|
189
180
|
this.deleteStmt.run([artifactId]);
|
|
190
|
-
this.deleteFtsStmt.run([artifactId]);
|
|
191
181
|
}
|
|
192
182
|
insertFilesForArtifact(artifactId, files) {
|
|
193
183
|
for (const file of files) {
|
|
@@ -199,7 +189,6 @@ export class FilesRepo {
|
|
|
199
189
|
file.contentBytes,
|
|
200
190
|
contentHash
|
|
201
191
|
]);
|
|
202
|
-
this.insertFtsStmt.run([artifactId, file.filePath, file.content]);
|
|
203
192
|
}
|
|
204
193
|
}
|
|
205
194
|
replaceFilesForArtifact(artifactId, files) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createError, ERROR_CODES } from "../errors.js";
|
|
2
|
-
import { SCHEMA_V1_STATEMENTS, SCHEMA_V2_STATEMENTS } from "./schema.js";
|
|
3
|
-
export const LATEST_SCHEMA_VERSION =
|
|
2
|
+
import { SCHEMA_V1_STATEMENTS, SCHEMA_V2_STATEMENTS, SCHEMA_V3_STATEMENTS, SCHEMA_V4_STATEMENTS } from "./schema.js";
|
|
3
|
+
export const LATEST_SCHEMA_VERSION = 4;
|
|
4
4
|
const migrations = [
|
|
5
5
|
{
|
|
6
6
|
version: 1,
|
|
@@ -9,6 +9,14 @@ const migrations = [
|
|
|
9
9
|
{
|
|
10
10
|
version: 2,
|
|
11
11
|
statements: SCHEMA_V2_STATEMENTS
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
version: 3,
|
|
15
|
+
statements: SCHEMA_V3_STATEMENTS
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
version: 4,
|
|
19
|
+
statements: SCHEMA_V4_STATEMENTS
|
|
12
20
|
}
|
|
13
21
|
];
|
|
14
22
|
function selectSchemaVersion(tx) {
|
package/dist/storage/schema.d.ts
CHANGED
package/dist/storage/schema.js
CHANGED
|
@@ -164,4 +164,29 @@ export const SCHEMA_V2_STATEMENTS = [
|
|
|
164
164
|
`DELETE FROM artifact_index_meta`,
|
|
165
165
|
`DELETE FROM artifacts`
|
|
166
166
|
];
|
|
167
|
+
export const SCHEMA_V4_STATEMENTS = [
|
|
168
|
+
`ALTER TABLE artifacts ADD COLUMN alias TEXT`,
|
|
169
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_artifacts_alias ON artifacts(alias) WHERE alias IS NOT NULL`
|
|
170
|
+
];
|
|
171
|
+
export const SCHEMA_V3_STATEMENTS = [
|
|
172
|
+
`DROP TABLE IF EXISTS files_fts`,
|
|
173
|
+
`CREATE VIRTUAL TABLE files_fts USING fts5(
|
|
174
|
+
artifact_id UNINDEXED,
|
|
175
|
+
file_path,
|
|
176
|
+
content,
|
|
177
|
+
content='files',
|
|
178
|
+
content_rowid='rowid',
|
|
179
|
+
tokenize = 'unicode61 separators ''._$'''
|
|
180
|
+
)`,
|
|
181
|
+
`CREATE TRIGGER IF NOT EXISTS trg_fts_insert AFTER INSERT ON files BEGIN
|
|
182
|
+
INSERT INTO files_fts(rowid, artifact_id, file_path, content)
|
|
183
|
+
VALUES (NEW.rowid, NEW.artifact_id, NEW.file_path, NEW.content);
|
|
184
|
+
END`,
|
|
185
|
+
// No UPDATE trigger: FilesRepo uses delete+insert, never updates rows in place.
|
|
186
|
+
`CREATE TRIGGER IF NOT EXISTS trg_fts_delete BEFORE DELETE ON files BEGIN
|
|
187
|
+
INSERT INTO files_fts(files_fts, rowid, artifact_id, file_path, content)
|
|
188
|
+
VALUES ('delete', OLD.rowid, OLD.artifact_id, OLD.file_path, OLD.content);
|
|
189
|
+
END`,
|
|
190
|
+
`INSERT INTO files_fts(files_fts) VALUES('rebuild')`
|
|
191
|
+
];
|
|
167
192
|
//# sourceMappingURL=schema.js.map
|
|
@@ -15,7 +15,7 @@ const SECTION_ROWS = {
|
|
|
15
15
|
"| `analyze-symbol` | Handle symbol existence checks, namespace mapping, lifecycle tracing, workspace symbol resolution, and API overviews |",
|
|
16
16
|
"| `compare-minecraft` | Compare version pairs, class diffs, registry diffs, and migration-oriented summaries |",
|
|
17
17
|
"| `analyze-mod` | Summarize mod metadata, decompile and search mod code, inspect class source, and preview or apply remaps |",
|
|
18
|
-
"| `validate-project` | Summarize workspaces and run direct Mixin or Access
|
|
18
|
+
"| `validate-project` | Summarize workspaces and run direct Mixin, Access Widener, or Access Transformer validation |",
|
|
19
19
|
"| `manage-cache` | List, verify, and preview or apply cache cleanup and rebuild operations |"
|
|
20
20
|
],
|
|
21
21
|
ja: [
|
|
@@ -23,7 +23,7 @@ const SECTION_ROWS = {
|
|
|
23
23
|
"| `analyze-symbol` | シンボル存在確認、名前空間変換、ライフサイクル追跡、ワークスペースシンボル解決、API 概要をまとめて扱う |",
|
|
24
24
|
"| `compare-minecraft` | バージョン差分、クラス差分、レジストリ差分、移行向け概要を比較する |",
|
|
25
25
|
"| `analyze-mod` | Mod メタデータの要約、Mod コードのデコンパイル / 検索、クラスソース確認、リマップのプレビュー / 実行を扱う |",
|
|
26
|
-
"| `validate-project` | ワークスペース要約と、Mixin / Access Widener の直接検証を行う |",
|
|
26
|
+
"| `validate-project` | ワークスペース要約と、Mixin / Access Widener / Access Transformer の直接検証を行う |",
|
|
27
27
|
"| `manage-cache` | キャッシュの一覧、検証、クリーンアップ / 再構築のプレビュー / 実行を行う |"
|
|
28
28
|
]
|
|
29
29
|
},
|
|
@@ -93,14 +93,14 @@ const SECTION_ROWS = {
|
|
|
93
93
|
},
|
|
94
94
|
"mod-analysis": {
|
|
95
95
|
en: [
|
|
96
|
-
"| `analyze-mod-jar` | Extract mod metadata, dependencies, entrypoints,
|
|
96
|
+
"| `analyze-mod-jar` | Extract mod metadata, dependencies, entrypoints, mixin config info, and packaged access transformer paths from a JAR |",
|
|
97
97
|
"| `decompile-mod-jar` | Decompile a mod JAR and optionally return one class source |",
|
|
98
98
|
"| `get-mod-class-source` | Read one class source from the decompiled mod cache |",
|
|
99
99
|
"| `search-mod-source` | Search decompiled mod source by class, method, field, or content |",
|
|
100
100
|
"| `remap-mod-jar` | Remap a Fabric or Quilt mod JAR to `yarn` or `mojang` names |"
|
|
101
101
|
],
|
|
102
102
|
ja: [
|
|
103
|
-
"| `analyze-mod-jar` | JAR から Mod メタデータ、依存関係、エントリポイント、Mixin
|
|
103
|
+
"| `analyze-mod-jar` | JAR から Mod メタデータ、依存関係、エントリポイント、Mixin 設定情報、同梱 Access Transformer パスを抽出する |",
|
|
104
104
|
"| `decompile-mod-jar` | Mod JAR をデコンパイルし、必要に応じて 1 つのクラスソースを返す |",
|
|
105
105
|
"| `get-mod-class-source` | デコンパイル済み Mod キャッシュから 1 つのクラスソースを読み取る |",
|
|
106
106
|
"| `search-mod-source` | デコンパイル済み Mod ソースを class、method、field、content で検索する |",
|
|
@@ -110,11 +110,13 @@ const SECTION_ROWS = {
|
|
|
110
110
|
"validation": {
|
|
111
111
|
en: [
|
|
112
112
|
"| `validate-mixin` | Validate Mixin source against a target Minecraft version |",
|
|
113
|
-
"| `validate-access-widener` | Validate Access Widener content against a target Minecraft version |"
|
|
113
|
+
"| `validate-access-widener` | Validate Access Widener content against a target Minecraft version, optionally using runtime-aware Loom artifacts |",
|
|
114
|
+
"| `validate-access-transformer` | Validate Access Transformer content against a target Minecraft version, optionally using Forge/NeoForge runtime artifacts |"
|
|
114
115
|
],
|
|
115
116
|
ja: [
|
|
116
117
|
"| `validate-mixin` | 対象 Minecraft バージョンに対して Mixin ソースを検証する |",
|
|
117
|
-
"| `validate-access-widener` | 対象 Minecraft バージョンに対して Access Widener
|
|
118
|
+
"| `validate-access-widener` | 対象 Minecraft バージョンに対して Access Widener の内容を検証し、必要に応じて Loom runtime artifact も使う |",
|
|
119
|
+
"| `validate-access-transformer` | 対象 Minecraft バージョンに対して Access Transformer の内容を検証し、必要に応じて Forge / NeoForge runtime artifact も使う |"
|
|
118
120
|
]
|
|
119
121
|
},
|
|
120
122
|
"registry-diagnostics": {
|
package/dist/types.d.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
export type SourceOrigin = "local-jar" | "local-m2" | "remote-repo" | "decompiled";
|
|
2
2
|
export type SourceMapping = "obfuscated" | "mojang" | "intermediary" | "yarn";
|
|
3
|
+
export type AccessTransformerNamespace = "srg" | "mojang" | "obfuscated";
|
|
4
|
+
export type RuntimeValidationNamespace = SourceMapping | AccessTransformerNamespace;
|
|
3
5
|
export type MappingSourcePriority = "loom-first" | "maven-first";
|
|
4
6
|
export type ArtifactTargetKind = "version" | "jar" | "coordinate";
|
|
5
7
|
export type ArtifactScope = "vanilla" | "merged" | "loader";
|
|
8
|
+
export type MappingVariant = "pass" | "mojang-remapped";
|
|
6
9
|
export interface SourceTargetInput {
|
|
7
10
|
kind: ArtifactTargetKind;
|
|
8
11
|
value: string;
|
|
9
12
|
}
|
|
10
13
|
export interface ResolvedSourceArtifact {
|
|
11
14
|
artifactId: string;
|
|
15
|
+
artifactAlias?: string;
|
|
12
16
|
artifactSignature: string;
|
|
13
17
|
origin: SourceOrigin;
|
|
14
18
|
binaryJarPath?: string;
|
|
@@ -37,6 +41,21 @@ export interface ArtifactProvenance {
|
|
|
37
41
|
};
|
|
38
42
|
transformChain: string[];
|
|
39
43
|
}
|
|
44
|
+
export interface RuntimeValidationProvenance<TMapping extends RuntimeValidationNamespace = RuntimeValidationNamespace> {
|
|
45
|
+
version: string;
|
|
46
|
+
jarPath: string;
|
|
47
|
+
requestedScope?: ArtifactScope;
|
|
48
|
+
appliedScope?: ArtifactScope;
|
|
49
|
+
requestedMapping: TMapping;
|
|
50
|
+
mappingApplied: TMapping;
|
|
51
|
+
origin: SourceOrigin | "loom-cache" | "version-jar";
|
|
52
|
+
resolutionNotes?: string[];
|
|
53
|
+
scopeFallback?: {
|
|
54
|
+
requested: string;
|
|
55
|
+
applied: string;
|
|
56
|
+
reason: string;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
40
59
|
export interface ErrorEnvelope {
|
|
41
60
|
code: string;
|
|
42
61
|
message: string;
|
|
@@ -85,6 +104,7 @@ export interface SourceSearchHit {
|
|
|
85
104
|
}
|
|
86
105
|
export interface ArtifactRow {
|
|
87
106
|
artifactId: string;
|
|
107
|
+
alias: string | undefined;
|
|
88
108
|
origin: SourceOrigin;
|
|
89
109
|
coordinate: string | undefined;
|
|
90
110
|
version: string | undefined;
|
|
@@ -13,7 +13,20 @@ export type WorkspaceCompileMappingOutput = {
|
|
|
13
13
|
evidence: WorkspaceMappingEvidence[];
|
|
14
14
|
warnings: string[];
|
|
15
15
|
};
|
|
16
|
+
export type WorkspaceProjectLoader = "fabric" | "quilt" | "forge" | "neoforge" | "unknown";
|
|
17
|
+
export type WorkspaceLoaderEvidence = {
|
|
18
|
+
filePath: string;
|
|
19
|
+
loader: WorkspaceProjectLoader;
|
|
20
|
+
reason: string;
|
|
21
|
+
};
|
|
22
|
+
export type WorkspaceProjectLoaderOutput = {
|
|
23
|
+
resolved: boolean;
|
|
24
|
+
loader?: WorkspaceProjectLoader;
|
|
25
|
+
evidence: WorkspaceLoaderEvidence[];
|
|
26
|
+
warnings: string[];
|
|
27
|
+
};
|
|
16
28
|
export declare class WorkspaceMappingService {
|
|
17
29
|
detectCompileMapping(input: WorkspaceCompileMappingInput): Promise<WorkspaceCompileMappingOutput>;
|
|
18
30
|
detectProjectMinecraftVersion(projectPath: string): Promise<string | undefined>;
|
|
31
|
+
detectProjectLoader(projectPath: string): Promise<WorkspaceProjectLoaderOutput>;
|
|
19
32
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import fastGlob from "fast-glob";
|
|
4
|
+
import { mapWithConcurrencyLimit } from "./concurrency.js";
|
|
4
5
|
import { createError, ERROR_CODES } from "./errors.js";
|
|
6
|
+
const WORKSPACE_FILE_READ_CONCURRENCY = 4;
|
|
5
7
|
function detectMappingsFromContent(content) {
|
|
6
8
|
const detections = [];
|
|
7
9
|
if (/officialMojangMappings\s*\(/i.test(content)) {
|
|
@@ -36,6 +38,46 @@ function detectMappingsFromContent(content) {
|
|
|
36
38
|
}
|
|
37
39
|
return detections;
|
|
38
40
|
}
|
|
41
|
+
function detectLoadersFromContent(content) {
|
|
42
|
+
const detections = [];
|
|
43
|
+
if (/\bid\s*(?:\(\s*)?["']net\.neoforged\.moddev["']\s*\)?/i.test(content)) {
|
|
44
|
+
detections.push({
|
|
45
|
+
loader: "neoforge",
|
|
46
|
+
reason: "net.neoforged.moddev plugin"
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (/\bid\s*(?:\(\s*)?["']net\.minecraftforge\.gradle["']\s*\)?/i.test(content)) {
|
|
50
|
+
detections.push({
|
|
51
|
+
loader: "forge",
|
|
52
|
+
reason: "net.minecraftforge.gradle plugin"
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (/\bid\s*(?:\(\s*)?["']org\.quiltmc\.loom["']\s*\)?/i.test(content)) {
|
|
56
|
+
detections.push({
|
|
57
|
+
loader: "quilt",
|
|
58
|
+
reason: "org.quiltmc.loom plugin"
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (/\bid\s*(?:\(\s*)?["']fabric-loom["']\s*\)?/i.test(content) || /\bid\s*(?:\(\s*)?["']dev\.architectury\.loom["']\s*\)?/i.test(content)) {
|
|
62
|
+
detections.push({
|
|
63
|
+
loader: "fabric",
|
|
64
|
+
reason: "fabric/dev.architectury loom plugin"
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (/\bminecraft\s*\{[\s\S]*?\baccessTransformer\b/i.test(content)) {
|
|
68
|
+
detections.push({
|
|
69
|
+
loader: "forge",
|
|
70
|
+
reason: "minecraft { accessTransformer ... } block"
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
if (/\bneoForge\s*\{[\s\S]*?\baccessTransformers\b/i.test(content)) {
|
|
74
|
+
detections.push({
|
|
75
|
+
loader: "neoforge",
|
|
76
|
+
reason: "neoForge { accessTransformers ... } block"
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return detections;
|
|
80
|
+
}
|
|
39
81
|
export class WorkspaceMappingService {
|
|
40
82
|
async detectCompileMapping(input) {
|
|
41
83
|
const projectPath = input.projectPath?.trim();
|
|
@@ -49,30 +91,26 @@ export class WorkspaceMappingService {
|
|
|
49
91
|
});
|
|
50
92
|
}
|
|
51
93
|
const root = resolve(projectPath);
|
|
52
|
-
const files = fastGlob.
|
|
94
|
+
const files = (await fastGlob.glob(["build.gradle", "build.gradle.kts", "**/build.gradle", "**/build.gradle.kts"], {
|
|
53
95
|
cwd: root,
|
|
54
96
|
absolute: true,
|
|
55
97
|
onlyFiles: true,
|
|
56
98
|
ignore: ["**/.git/**", "**/.gradle/**", "**/build/**", "**/out/**", "**/node_modules/**"]
|
|
57
|
-
});
|
|
58
|
-
const evidence =
|
|
59
|
-
for (const filePath of files.sort((left, right) => left.localeCompare(right))) {
|
|
99
|
+
})).sort((left, right) => left.localeCompare(right));
|
|
100
|
+
const evidence = (await mapWithConcurrencyLimit(files, WORKSPACE_FILE_READ_CONCURRENCY, async (filePath) => {
|
|
60
101
|
let content;
|
|
61
102
|
try {
|
|
62
103
|
content = await readFile(filePath, "utf8");
|
|
63
104
|
}
|
|
64
105
|
catch {
|
|
65
|
-
|
|
106
|
+
return [];
|
|
66
107
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
}
|
|
108
|
+
return detectMappingsFromContent(content).map((detection) => ({
|
|
109
|
+
filePath,
|
|
110
|
+
mapping: detection.mapping,
|
|
111
|
+
reason: detection.reason
|
|
112
|
+
}));
|
|
113
|
+
})).flat();
|
|
76
114
|
if (evidence.length === 0) {
|
|
77
115
|
return {
|
|
78
116
|
resolved: false,
|
|
@@ -121,5 +159,99 @@ export class WorkspaceMappingService {
|
|
|
121
159
|
}
|
|
122
160
|
return undefined;
|
|
123
161
|
}
|
|
162
|
+
async detectProjectLoader(projectPath) {
|
|
163
|
+
const root = resolve(projectPath);
|
|
164
|
+
const buildFiles = (await fastGlob.glob(["build.gradle", "build.gradle.kts", "**/build.gradle", "**/build.gradle.kts"], {
|
|
165
|
+
cwd: root,
|
|
166
|
+
absolute: true,
|
|
167
|
+
onlyFiles: true,
|
|
168
|
+
ignore: ["**/.git/**", "**/.gradle/**", "**/build/**", "**/out/**", "**/node_modules/**"]
|
|
169
|
+
})).sort((left, right) => left.localeCompare(right));
|
|
170
|
+
const descriptorFiles = (await fastGlob.glob([
|
|
171
|
+
"fabric.mod.json",
|
|
172
|
+
"quilt.mod.json",
|
|
173
|
+
"META-INF/mods.toml",
|
|
174
|
+
"META-INF/neoforge.mods.toml",
|
|
175
|
+
"**/fabric.mod.json",
|
|
176
|
+
"**/quilt.mod.json",
|
|
177
|
+
"**/META-INF/mods.toml",
|
|
178
|
+
"**/META-INF/neoforge.mods.toml"
|
|
179
|
+
], {
|
|
180
|
+
cwd: root,
|
|
181
|
+
absolute: true,
|
|
182
|
+
onlyFiles: true,
|
|
183
|
+
ignore: ["**/.git/**", "**/.gradle/**", "**/build/**", "**/out/**", "**/node_modules/**"]
|
|
184
|
+
})).sort((left, right) => left.localeCompare(right));
|
|
185
|
+
const evidence = (await mapWithConcurrencyLimit(buildFiles, WORKSPACE_FILE_READ_CONCURRENCY, async (filePath) => {
|
|
186
|
+
let content;
|
|
187
|
+
try {
|
|
188
|
+
content = await readFile(filePath, "utf8");
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
return detectLoadersFromContent(content).map((detection) => ({
|
|
194
|
+
filePath,
|
|
195
|
+
loader: detection.loader,
|
|
196
|
+
reason: detection.reason
|
|
197
|
+
}));
|
|
198
|
+
})).flat();
|
|
199
|
+
for (const descriptorPath of descriptorFiles) {
|
|
200
|
+
const normalized = descriptorPath.replaceAll("\\", "/");
|
|
201
|
+
if (normalized.endsWith("fabric.mod.json")) {
|
|
202
|
+
evidence.push({
|
|
203
|
+
filePath: descriptorPath,
|
|
204
|
+
loader: "fabric",
|
|
205
|
+
reason: "fabric.mod.json"
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
else if (normalized.endsWith("quilt.mod.json")) {
|
|
209
|
+
evidence.push({
|
|
210
|
+
filePath: descriptorPath,
|
|
211
|
+
loader: "quilt",
|
|
212
|
+
reason: "quilt.mod.json"
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
else if (normalized.endsWith("META-INF/neoforge.mods.toml")) {
|
|
216
|
+
evidence.push({
|
|
217
|
+
filePath: descriptorPath,
|
|
218
|
+
loader: "neoforge",
|
|
219
|
+
reason: "META-INF/neoforge.mods.toml"
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
else if (normalized.endsWith("META-INF/mods.toml")) {
|
|
223
|
+
evidence.push({
|
|
224
|
+
filePath: descriptorPath,
|
|
225
|
+
loader: "forge",
|
|
226
|
+
reason: "META-INF/mods.toml"
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (evidence.length === 0) {
|
|
231
|
+
return {
|
|
232
|
+
resolved: false,
|
|
233
|
+
evidence,
|
|
234
|
+
warnings: ["No workspace loader declaration was detected from build.gradle(.kts) files or mod descriptors."]
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
const loaderSet = new Set(evidence
|
|
238
|
+
.map((entry) => entry.loader)
|
|
239
|
+
.filter((loader) => loader !== "unknown"));
|
|
240
|
+
if (loaderSet.size !== 1) {
|
|
241
|
+
return {
|
|
242
|
+
resolved: false,
|
|
243
|
+
evidence,
|
|
244
|
+
warnings: [
|
|
245
|
+
`Multiple or ambiguous workspace loaders were detected: ${[...loaderSet].join(", ") || "unknown"}.`
|
|
246
|
+
]
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
resolved: true,
|
|
251
|
+
loader: [...loaderSet][0],
|
|
252
|
+
evidence,
|
|
253
|
+
warnings: []
|
|
254
|
+
};
|
|
255
|
+
}
|
|
124
256
|
}
|
|
125
257
|
//# sourceMappingURL=workspace-mapping-service.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhisang/minecraft-modding-mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "MCP server with utilities for Minecraft modding workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
"start": "node dist/cli.js",
|
|
24
24
|
"check": "tsc --noEmit -p tsconfig.json",
|
|
25
25
|
"test": "node --test --import tsx tests/*.test.ts",
|
|
26
|
+
"test:file": "node --test --import tsx",
|
|
27
|
+
"test:grep": "node --test --import tsx --test-name-pattern",
|
|
26
28
|
"test:coverage": "node --test --import tsx --experimental-test-coverage --test-coverage-lines=80 --test-coverage-branches=70 --test-coverage-functions=80 tests/*.test.ts",
|
|
27
29
|
"test:coverage:lcov": "node --input-type=module -e \"import { mkdirSync } from 'node:fs'; mkdirSync('coverage', { recursive: true });\" && node --test --import tsx --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info --test-coverage-lines=80 --test-coverage-branches=70 --test-coverage-functions=80 tests/*.test.ts",
|
|
28
30
|
"test:perf": "node --test --test-concurrency=1 --import tsx tests/perf/*.perf.ts",
|