@adhisang/minecraft-modding-mcp 4.2.0 → 5.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 +40 -0
- package/README.md +6 -4
- package/dist/access-transformer-parser.d.ts +2 -1
- package/dist/access-transformer-parser.js +10 -1
- package/dist/build-suggested-call.js +31 -10
- package/dist/cache-registry.d.ts +8 -0
- package/dist/cache-registry.js +99 -6
- package/dist/config.d.ts +1 -0
- package/dist/config.js +4 -0
- package/dist/entry-tools/analyze-mod-service.d.ts +24 -24
- package/dist/entry-tools/analyze-symbol-service.d.ts +57 -22
- package/dist/entry-tools/analyze-symbol-service.js +124 -28
- package/dist/entry-tools/batch-class-members-service.d.ts +3 -1
- package/dist/entry-tools/batch-class-members-service.js +6 -6
- package/dist/entry-tools/batch-class-source-service.d.ts +3 -1
- package/dist/entry-tools/batch-class-source-service.js +5 -6
- package/dist/entry-tools/batch-mappings-service.d.ts +3 -1
- package/dist/entry-tools/batch-mappings-service.js +4 -5
- package/dist/entry-tools/batch-symbol-exists-service.d.ts +3 -1
- package/dist/entry-tools/batch-symbol-exists-service.js +4 -5
- package/dist/entry-tools/compare-minecraft-service.d.ts +10 -10
- package/dist/entry-tools/entry-tool-schema.d.ts +1 -1
- package/dist/entry-tools/inspect-minecraft/handlers/artifact.js +1 -3
- package/dist/entry-tools/inspect-minecraft/handlers/class-members.js +2 -2
- package/dist/entry-tools/inspect-minecraft/handlers/class-overview.js +0 -5
- package/dist/entry-tools/inspect-minecraft/handlers/class-source.js +0 -1
- package/dist/entry-tools/inspect-minecraft/handlers/file.js +0 -1
- package/dist/entry-tools/inspect-minecraft/handlers/list-files.js +0 -1
- package/dist/entry-tools/inspect-minecraft/handlers/search.d.ts +8 -0
- package/dist/entry-tools/inspect-minecraft/handlers/search.js +13 -2
- package/dist/entry-tools/inspect-minecraft/handlers/versions.d.ts +1 -1
- package/dist/entry-tools/inspect-minecraft/handlers/versions.js +1 -1
- package/dist/entry-tools/inspect-minecraft/internal.d.ts +90 -89
- package/dist/entry-tools/inspect-minecraft/internal.js +30 -14
- package/dist/entry-tools/inspect-minecraft-service.d.ts +218 -218
- package/dist/entry-tools/inspect-minecraft-service.js +1 -1
- package/dist/entry-tools/manage-cache-service.d.ts +4 -4
- package/dist/entry-tools/response-contract.d.ts +1 -1
- package/dist/entry-tools/response-contract.js +1 -0
- package/dist/entry-tools/validate-project/cases/mixin.js +2 -1
- package/dist/entry-tools/validate-project/cases/project-summary.js +5 -3
- package/dist/entry-tools/validate-project-service.d.ts +42 -36
- package/dist/entry-tools/validate-project-service.js +3 -1
- package/dist/entry-tools/verify-mixin-target-service.d.ts +30 -0
- package/dist/entry-tools/verify-mixin-target-service.js +113 -22
- package/dist/error-mapping.d.ts +51 -0
- package/dist/error-mapping.js +157 -2
- package/dist/errors.js +4 -1
- package/dist/index.js +138 -71
- package/dist/java-process.d.ts +9 -0
- package/dist/java-process.js +11 -5
- package/dist/mapping/internal-types.d.ts +7 -0
- package/dist/mapping/lookup.d.ts +1 -1
- package/dist/mapping/lookup.js +7 -1
- package/dist/mapping/types.d.ts +6 -0
- package/dist/mapping-service.d.ts +26 -2
- package/dist/mapping-service.js +178 -36
- package/dist/mcp-helpers.d.ts +1 -0
- package/dist/mcp-helpers.js +25 -3
- package/dist/minecraft-explorer-service.js +24 -11
- package/dist/mixin/access-validators.js +33 -1
- package/dist/mixin/annotation-validators.js +53 -7
- package/dist/mixin/parsed-validator.js +2 -2
- package/dist/mixin-parser.js +48 -4
- package/dist/mod-decompile-service.js +2 -1
- package/dist/mod-remap-service.d.ts +20 -1
- package/dist/mod-remap-service.js +95 -40
- package/dist/nbt/java-nbt-codec.js +87 -2
- package/dist/observability.d.ts +6 -0
- package/dist/observability.js +10 -0
- package/dist/page-cursor.d.ts +17 -0
- package/dist/page-cursor.js +0 -0
- package/dist/registry-service.js +25 -1
- package/dist/resources.js +45 -9
- package/dist/response-utils.d.ts +29 -4
- package/dist/response-utils.js +95 -5
- package/dist/source/access-validate.js +12 -2
- package/dist/source/artifact-resolver.js +1 -1
- package/dist/source/cache-metrics.js +2 -2
- package/dist/source/class-source/members-builder.d.ts +44 -1
- package/dist/source/class-source/members-builder.js +51 -4
- package/dist/source/class-source/snippet-builder.d.ts +13 -0
- package/dist/source/class-source/snippet-builder.js +48 -4
- package/dist/source/class-source-helpers.js +67 -11
- package/dist/source/class-source.js +97 -20
- package/dist/source/indexer.d.ts +31 -6
- package/dist/source/indexer.js +95 -25
- package/dist/source/search.d.ts +1 -1
- package/dist/source/search.js +94 -22
- package/dist/source/validate-mixin.d.ts +2 -1
- package/dist/source/validate-mixin.js +45 -4
- package/dist/source/workspace-target.js +15 -9
- package/dist/source-jar-reader.d.ts +54 -1
- package/dist/source-jar-reader.js +121 -10
- package/dist/source-service.d.ts +28 -7
- package/dist/storage/files-repo.d.ts +12 -0
- package/dist/storage/files-repo.js +24 -0
- package/dist/storage/symbols-repo.d.ts +6 -0
- package/dist/storage/symbols-repo.js +6 -0
- package/dist/symbols/symbol-extractor.js +35 -6
- package/dist/text-truncate.d.ts +7 -0
- package/dist/text-truncate.js +21 -0
- package/dist/tiny-remapper-resolver.js +10 -0
- package/dist/tiny-remapper-service.js +3 -3
- package/dist/tool-contract-manifest.js +2 -2
- package/dist/tool-execution-gate.js +12 -1
- package/dist/tool-guidance.d.ts +4 -1
- package/dist/tool-guidance.js +37 -12
- package/dist/tool-schemas.d.ts +559 -494
- package/dist/tool-schemas.js +99 -63
- package/dist/types.d.ts +1 -0
- package/dist/version-diff-service.js +5 -0
- package/dist/version-service.d.ts +3 -0
- package/dist/version-service.js +18 -2
- package/dist/warning-details.d.ts +18 -0
- package/dist/warning-details.js +63 -0
- package/docs/README-ja.md +2 -2
- package/docs/examples.md +26 -2
- package/docs/tool-reference.md +23 -17
- package/package.json +1 -1
|
@@ -47,6 +47,7 @@ export declare class FilesRepo {
|
|
|
47
47
|
private readonly listStmt;
|
|
48
48
|
private readonly listRowsStmt;
|
|
49
49
|
private readonly searchPathStmt;
|
|
50
|
+
private readonly searchContentLikePathsStmt;
|
|
50
51
|
private readonly searchFtsStmt;
|
|
51
52
|
private readonly getByPathsStmtCache;
|
|
52
53
|
private readonly classLookupStmtCache;
|
|
@@ -58,6 +59,17 @@ export declare class FilesRepo {
|
|
|
58
59
|
getFileContent(artifactId: string, filePath: string): FileRow | undefined;
|
|
59
60
|
listFiles(artifactId: string, options: ListFilesOptions): PagedResult<string>;
|
|
60
61
|
listFileRows(artifactId: string, options: ListFileRowsOptions): PagedResult<FileRow>;
|
|
62
|
+
/**
|
|
63
|
+
* Narrow the literal/contains text-scan candidate set without hydrating content:
|
|
64
|
+
* returns the file_paths whose content matches `content LIKE %needle%` (ASCII
|
|
65
|
+
* case-insensitive). The result is a SUPERSET of the JS contains/exact match set
|
|
66
|
+
* for an ASCII needle, so callers MUST still run the exact JS post-verify. Only
|
|
67
|
+
* file_path is selected — content stays in the DB until getFileContentsByPaths.
|
|
68
|
+
*/
|
|
69
|
+
searchContentLikeCandidatePaths(artifactId: string, needle: string, limit: number): {
|
|
70
|
+
filePaths: string[];
|
|
71
|
+
scannedRows: number;
|
|
72
|
+
};
|
|
61
73
|
getFileContentsByPaths(artifactId: string, filePaths: string[]): FileRow[];
|
|
62
74
|
searchFileCandidates(artifactId: string, options: SearchFilesOptions): PagedResult<SearchFileCandidateResult> & {
|
|
63
75
|
scannedRows: number;
|
|
@@ -121,6 +121,10 @@ function buildIndexedMatchQuery(query, match) {
|
|
|
121
121
|
return token;
|
|
122
122
|
}).join(" ");
|
|
123
123
|
}
|
|
124
|
+
/** Escape LIKE wildcards so the needle is matched literally under ESCAPE '\'. */
|
|
125
|
+
function escapeLikeNeedle(value) {
|
|
126
|
+
return value.replace(/[\\%_]/g, "\\$&");
|
|
127
|
+
}
|
|
124
128
|
export class FilesRepo {
|
|
125
129
|
db;
|
|
126
130
|
deleteStmt;
|
|
@@ -129,6 +133,7 @@ export class FilesRepo {
|
|
|
129
133
|
listStmt;
|
|
130
134
|
listRowsStmt;
|
|
131
135
|
searchPathStmt;
|
|
136
|
+
searchContentLikePathsStmt;
|
|
132
137
|
searchFtsStmt;
|
|
133
138
|
getByPathsStmtCache = new Map();
|
|
134
139
|
classLookupStmtCache = new Map();
|
|
@@ -167,6 +172,13 @@ export class FilesRepo {
|
|
|
167
172
|
WHERE artifact_id = ? AND file_path LIKE ? ESCAPE '\\'
|
|
168
173
|
ORDER BY file_path ASC
|
|
169
174
|
LIMIT ?
|
|
175
|
+
`);
|
|
176
|
+
this.searchContentLikePathsStmt = this.db.prepare(`
|
|
177
|
+
SELECT file_path
|
|
178
|
+
FROM files
|
|
179
|
+
WHERE artifact_id = ? AND content LIKE ? ESCAPE '\\'
|
|
180
|
+
ORDER BY file_path ASC
|
|
181
|
+
LIMIT ?
|
|
170
182
|
`);
|
|
171
183
|
this.searchFtsStmt = this.db.prepare(`
|
|
172
184
|
SELECT file_path, rank
|
|
@@ -239,6 +251,18 @@ export class FilesRepo {
|
|
|
239
251
|
nextCursor: nextCursorFromRows(rows)
|
|
240
252
|
};
|
|
241
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* Narrow the literal/contains text-scan candidate set without hydrating content:
|
|
256
|
+
* returns the file_paths whose content matches `content LIKE %needle%` (ASCII
|
|
257
|
+
* case-insensitive). The result is a SUPERSET of the JS contains/exact match set
|
|
258
|
+
* for an ASCII needle, so callers MUST still run the exact JS post-verify. Only
|
|
259
|
+
* file_path is selected — content stays in the DB until getFileContentsByPaths.
|
|
260
|
+
*/
|
|
261
|
+
searchContentLikeCandidatePaths(artifactId, needle, limit) {
|
|
262
|
+
const pattern = `%${escapeLikeNeedle(needle)}%`;
|
|
263
|
+
const rows = this.searchContentLikePathsStmt.all(artifactId, pattern, Math.max(1, limit));
|
|
264
|
+
return { filePaths: rows.map((row) => row.file_path), scannedRows: rows.length };
|
|
265
|
+
}
|
|
242
266
|
getFileContentsByPaths(artifactId, filePaths) {
|
|
243
267
|
if (filePaths.length === 0) {
|
|
244
268
|
return [];
|
|
@@ -22,6 +22,12 @@ export interface FindScopedSymbolsOptions {
|
|
|
22
22
|
query: string;
|
|
23
23
|
match: ScopedSymbolMatch;
|
|
24
24
|
symbolKind?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Restrict results to these symbol kinds via a bound `symbol_kind IN (...)` clause.
|
|
27
|
+
* Combined with the singular `symbolKind` it ANDs (both apply). An empty array is
|
|
28
|
+
* ignored (degrades to no kind filter) to avoid emitting an invalid `IN ()`.
|
|
29
|
+
*/
|
|
30
|
+
symbolKinds?: string[];
|
|
25
31
|
packagePrefix?: string;
|
|
26
32
|
filePathLike?: string;
|
|
27
33
|
limit?: number;
|
|
@@ -260,6 +260,12 @@ export class SymbolsRepo {
|
|
|
260
260
|
where.push("symbol_kind = ?");
|
|
261
261
|
params.push(options.symbolKind);
|
|
262
262
|
}
|
|
263
|
+
const symbolKinds = options.symbolKinds;
|
|
264
|
+
if (symbolKinds && symbolKinds.length > 0) {
|
|
265
|
+
const placeholders = symbolKinds.map(() => "?").join(", ");
|
|
266
|
+
where.push(`symbol_kind IN (${placeholders})`);
|
|
267
|
+
params.push(...symbolKinds);
|
|
268
|
+
}
|
|
263
269
|
if (options.packagePrefix?.trim()) {
|
|
264
270
|
const normalizedPrefix = options.packagePrefix.replace(/\.+/g, "/").replace(/\/+$/, "");
|
|
265
271
|
where.push("file_path LIKE ? ESCAPE '\\'");
|
|
@@ -1,6 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
1
|
+
// A type token may carry spaced generics (Map<String, Integer>), arrays (int[][]),
|
|
2
|
+
// wildcards (List<? extends Foo>), and FQNs (java.util.Map). It must not contain "=".
|
|
3
|
+
const TYPE_TOKEN = "[\\w.$][\\w.$<>\\[\\],?\\s]*";
|
|
4
|
+
const MODIFIER = "(?:public|private|protected|abstract|final|static|native|synchronized|default|strictfp|transient|volatile)";
|
|
5
|
+
const MODIFIER_OR_ANNOTATION_RUN = `(?:(?:@[\\w.]+|${MODIFIER})\\s+)*`;
|
|
6
|
+
const TYPE_PARAMS = "(?:<[^>]+>\\s*)?";
|
|
7
|
+
const THROWS_CLAUSE = "(?:\\s+throws\\s+[\\w.$,\\s]+)?";
|
|
8
|
+
const CLASS_DECLARATION = new RegExp(`^${MODIFIER_OR_ANNOTATION_RUN}(class|interface|enum|record)\\s+([A-Za-z_$][\\w$]*)`);
|
|
9
|
+
// Method/constructor with a body: return type is optional (constructors have none); ends with "{".
|
|
10
|
+
const METHOD_BLOCK_DECLARATION = new RegExp(`^${MODIFIER_OR_ANNOTATION_RUN}${TYPE_PARAMS}(?:${TYPE_TOKEN}\\s+)?([A-Za-z_$][\\w$]*)\\s*\\([^)]*\\)${THROWS_CLAUSE}\\s*\\{`);
|
|
11
|
+
// Abstract/interface method declaration: a return type is REQUIRED; ends with ";".
|
|
12
|
+
// Requiring the return type is what separates "void onTick();" from a bare call "doThing();".
|
|
13
|
+
const METHOD_ABSTRACT_DECLARATION = new RegExp(`^${MODIFIER_OR_ANNOTATION_RUN}${TYPE_PARAMS}${TYPE_TOKEN}\\s+([A-Za-z_$][\\w$]*)\\s*\\([^)]*\\)${THROWS_CLAUSE}\\s*;`);
|
|
14
|
+
const FIELD_DECLARATION = new RegExp(`^${MODIFIER_OR_ANNOTATION_RUN}${TYPE_TOKEN}\\s+([A-Za-z_$][\\w$]*)\\s*(?:=|;|,)`);
|
|
15
|
+
// Lines that begin with one of these keywords are statements, not declarations. They are
|
|
16
|
+
// skipped wholesale so that "return helper();" / "for (...)" / "new Foo() {" never produce
|
|
17
|
+
// phantom members. "default"/"synchronized" are intentionally excluded (they double as
|
|
18
|
+
// member modifiers); the method regexes reject their statement forms structurally.
|
|
19
|
+
const STATEMENT_LINE_KEYWORDS = new Set([
|
|
20
|
+
"if", "for", "while", "switch", "catch", "return", "throw", "else", "do",
|
|
21
|
+
"case", "break", "continue", "assert", "super", "this", "try", "new", "yield"
|
|
22
|
+
]);
|
|
23
|
+
// Defence-in-depth: even if a keyword is captured as a member name, drop it.
|
|
4
24
|
const NOISE_TOKENS = new Set(["if", "for", "while", "switch", "catch", "return", "new", "throw"]);
|
|
5
25
|
function normalizeLine(line) {
|
|
6
26
|
return line.replace(/\s+/g, " ").trim();
|
|
@@ -8,6 +28,10 @@ function normalizeLine(line) {
|
|
|
8
28
|
function isNoiseToken(token) {
|
|
9
29
|
return NOISE_TOKENS.has(token);
|
|
10
30
|
}
|
|
31
|
+
function isStatementLine(line) {
|
|
32
|
+
const firstWord = /^([A-Za-z_$][\w$]*)/.exec(line)?.[1];
|
|
33
|
+
return firstWord != null && STATEMENT_LINE_KEYWORDS.has(firstWord);
|
|
34
|
+
}
|
|
11
35
|
function lineIndexToLine(lineNo) {
|
|
12
36
|
return lineNo + 1;
|
|
13
37
|
}
|
|
@@ -17,8 +41,13 @@ export function extractSymbolsFromSource(filePath, content) {
|
|
|
17
41
|
const qualifiedName = filePath.replace(/\.java$/, "").replaceAll("/", ".");
|
|
18
42
|
for (let index = 0; index < lines.length; index += 1) {
|
|
19
43
|
const rawLine = lines[index] ?? "";
|
|
20
|
-
const
|
|
21
|
-
if (!
|
|
44
|
+
const normalized = normalizeLine(rawLine);
|
|
45
|
+
if (!normalized) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
// Drop a single leading "}"/"{" so "} public void foo() {" still parses.
|
|
49
|
+
const line = normalized.replace(/^[}{]\s*/, "");
|
|
50
|
+
if (!line || isStatementLine(line)) {
|
|
22
51
|
continue;
|
|
23
52
|
}
|
|
24
53
|
const classMatch = line.match(CLASS_DECLARATION);
|
|
@@ -35,7 +64,7 @@ export function extractSymbolsFromSource(filePath, content) {
|
|
|
35
64
|
}
|
|
36
65
|
continue;
|
|
37
66
|
}
|
|
38
|
-
const methodMatch = line.match(
|
|
67
|
+
const methodMatch = line.match(METHOD_BLOCK_DECLARATION) ?? line.match(METHOD_ABSTRACT_DECLARATION);
|
|
39
68
|
if (methodMatch) {
|
|
40
69
|
const symbolName = methodMatch[1];
|
|
41
70
|
if (symbolName && !isNoiseToken(symbolName)) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slice `text` to at most `maxChars` UTF-16 code units without splitting a
|
|
3
|
+
* surrogate pair. A naive `text.slice(0, maxChars)` can leave a lone high
|
|
4
|
+
* surrogate at the boundary (e.g. when an emoji straddles the cut), producing
|
|
5
|
+
* an invalid string; this drops that dangling half so callers never emit one.
|
|
6
|
+
*/
|
|
7
|
+
export declare function sliceToMaxCharsSafe(text: string, maxChars: number): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slice `text` to at most `maxChars` UTF-16 code units without splitting a
|
|
3
|
+
* surrogate pair. A naive `text.slice(0, maxChars)` can leave a lone high
|
|
4
|
+
* surrogate at the boundary (e.g. when an emoji straddles the cut), producing
|
|
5
|
+
* an invalid string; this drops that dangling half so callers never emit one.
|
|
6
|
+
*/
|
|
7
|
+
export function sliceToMaxCharsSafe(text, maxChars) {
|
|
8
|
+
if (maxChars <= 0) {
|
|
9
|
+
return "";
|
|
10
|
+
}
|
|
11
|
+
if (text.length <= maxChars) {
|
|
12
|
+
return text;
|
|
13
|
+
}
|
|
14
|
+
let cut = text.slice(0, maxChars);
|
|
15
|
+
const lastUnit = cut.charCodeAt(cut.length - 1);
|
|
16
|
+
if (lastUnit >= 0xd800 && lastUnit <= 0xdbff) {
|
|
17
|
+
cut = cut.slice(0, -1);
|
|
18
|
+
}
|
|
19
|
+
return cut;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=text-truncate.js.map
|
|
@@ -15,6 +15,16 @@ function tinyRemapperDownloadUrl(version) {
|
|
|
15
15
|
export async function resolveTinyRemapperJar(cacheDir, overridePath, fetchFn) {
|
|
16
16
|
// 1. Environment / config override
|
|
17
17
|
if (overridePath) {
|
|
18
|
+
if (!existsSync(overridePath)) {
|
|
19
|
+
throw createError({
|
|
20
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
21
|
+
message: `Configured tiny-remapper jar does not exist: ${overridePath}`,
|
|
22
|
+
details: {
|
|
23
|
+
path: overridePath,
|
|
24
|
+
nextAction: "Point tinyRemapperJarPath / MCP_TINY_REMAPPER_JAR at an existing fat jar, or unset it to download automatically."
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
18
28
|
return overridePath;
|
|
19
29
|
}
|
|
20
30
|
const version = process.env.MCP_TINY_REMAPPER_VERSION ?? DEFAULT_VERSION;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { createError, ERROR_CODES } from "./errors.js";
|
|
3
|
-
import {
|
|
3
|
+
import { javaRunner } from "./java-process.js";
|
|
4
4
|
import { log } from "./logger.js";
|
|
5
5
|
export async function remapJar(tinyRemapperJarPath, options) {
|
|
6
6
|
const { inputJar, outputJar, mappingsFile, fromNamespace, toNamespace, threads = 4, rebuildSourceFilenames = false, timeoutMs = 600_000, maxMemoryMb = 4096 } = options;
|
|
7
|
-
await
|
|
7
|
+
await javaRunner.assertAvailable();
|
|
8
8
|
log("info", "remap.start", {
|
|
9
9
|
inputJar,
|
|
10
10
|
outputJar,
|
|
@@ -21,7 +21,7 @@ export async function remapJar(tinyRemapperJarPath, options) {
|
|
|
21
21
|
args.push("--rebuildSourceFilenames");
|
|
22
22
|
}
|
|
23
23
|
try {
|
|
24
|
-
const result = await
|
|
24
|
+
const result = await javaRunner.run({
|
|
25
25
|
jarPath: tinyRemapperJarPath,
|
|
26
26
|
args,
|
|
27
27
|
timeoutMs,
|
|
@@ -67,14 +67,14 @@ const SECTION_ROWS = {
|
|
|
67
67
|
"mapping-symbols": {
|
|
68
68
|
en: [
|
|
69
69
|
"| `find-mapping` | Look up mapping candidates for class, field, or method symbols |",
|
|
70
|
-
"| `resolve-method-mapping-exact` |
|
|
70
|
+
"| `resolve-method-mapping-exact` | Strict shortcut for `find-mapping` (kind=method, signatureMode=exact); requires the full owner+name+descriptor triple |",
|
|
71
71
|
"| `get-class-api-matrix` | Show one class API across `obfuscated`, `mojang`, `intermediary`, and `yarn` |",
|
|
72
72
|
"| `resolve-workspace-symbol` | Resolve compile-visible symbol names from a Gradle workspace |",
|
|
73
73
|
"| `check-symbol-exists` | Check whether a class, field, or method exists in a namespace |"
|
|
74
74
|
],
|
|
75
75
|
ja: [
|
|
76
76
|
"| `find-mapping` | クラス、フィールド、メソッドのシンボルに対するマッピング候補を調べる |",
|
|
77
|
-
"| `resolve-method-mapping-exact` | owner
|
|
77
|
+
"| `resolve-method-mapping-exact` | `find-mapping`(kind=method, signatureMode=exact)の厳密版ショートカット。owner+name+descriptor の三つ組が必須 |",
|
|
78
78
|
"| `get-class-api-matrix` | 1 つのクラス API を `obfuscated`、`mojang`、`intermediary`、`yarn` で見比べる |",
|
|
79
79
|
"| `resolve-workspace-symbol` | Gradle ワークスペースからコンパイル時に見えるシンボル名を解決する |",
|
|
80
80
|
"| `check-symbol-exists` | 名前空間内でクラス、フィールド、メソッドが存在するかを確認する |"
|
|
@@ -3,6 +3,13 @@ const DEFAULT_OPTIONS = {
|
|
|
3
3
|
maxConcurrent: 1,
|
|
4
4
|
maxQueue: 2
|
|
5
5
|
};
|
|
6
|
+
// Heavy tools that have a batch equivalent able to collapse many same-kind
|
|
7
|
+
// queries into a single gated call. Used to point overflow guidance at the
|
|
8
|
+
// right batch-* tool instead of just telling the caller to retry serially.
|
|
9
|
+
const BATCH_EQUIVALENTS = {
|
|
10
|
+
"find-mapping": "batch-mappings",
|
|
11
|
+
"resolve-method-mapping-exact": "batch-mappings"
|
|
12
|
+
};
|
|
6
13
|
export class ToolExecutionGate {
|
|
7
14
|
maxConcurrent;
|
|
8
15
|
maxQueue;
|
|
@@ -17,6 +24,10 @@ export class ToolExecutionGate {
|
|
|
17
24
|
return this.execute({ tool, task: task });
|
|
18
25
|
}
|
|
19
26
|
if (this.queue.length >= this.maxQueue) {
|
|
27
|
+
const batchTool = BATCH_EQUIVALENTS[tool];
|
|
28
|
+
const nextAction = batchTool
|
|
29
|
+
? `Retry after the current heavy analysis request completes. To run many ${tool} queries efficiently, collapse them into a single ${batchTool} call instead of issuing them in parallel.`
|
|
30
|
+
: "Retry after the current heavy analysis request completes. Avoid sending multiple heavy mapping/version analysis tools in parallel.";
|
|
20
31
|
return Promise.reject(createError({
|
|
21
32
|
code: ERROR_CODES.LIMIT_EXCEEDED,
|
|
22
33
|
message: `Heavy tool queue is full; "${tool}" was not started.`,
|
|
@@ -26,7 +37,7 @@ export class ToolExecutionGate {
|
|
|
26
37
|
queuedCount: this.queue.length,
|
|
27
38
|
maxConcurrent: this.maxConcurrent,
|
|
28
39
|
maxQueue: this.maxQueue,
|
|
29
|
-
nextAction
|
|
40
|
+
nextAction
|
|
30
41
|
}
|
|
31
42
|
}));
|
|
32
43
|
}
|
package/dist/tool-guidance.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ZodError } from "zod";
|
|
2
|
+
import type { WarningDetail } from "./warning-details.js";
|
|
2
3
|
import { type ExampleCall, type ProblemDetails, type ProblemFieldError, type SuggestedCall } from "./error-mapping.js";
|
|
3
4
|
import type { SourceTargetInput } from "./types.js";
|
|
4
5
|
export type ToolMeta = {
|
|
@@ -6,6 +7,8 @@ export type ToolMeta = {
|
|
|
6
7
|
tool: string;
|
|
7
8
|
durationMs: number;
|
|
8
9
|
warnings: string[];
|
|
10
|
+
/** Structured companion to `warnings`: one classified entry per warning string, referencing its text via `warnings[detail.index]`. */
|
|
11
|
+
warningDetails?: WarningDetail[];
|
|
9
12
|
detailApplied?: "summary" | "standard" | "full";
|
|
10
13
|
includeApplied?: string[];
|
|
11
14
|
truncated?: Record<string, unknown>;
|
|
@@ -29,7 +32,7 @@ export declare const SUGGESTED_CALL_DEFAULTS: {
|
|
|
29
32
|
readonly explain: false;
|
|
30
33
|
readonly preferProjectMapping: false;
|
|
31
34
|
readonly minSeverity: "all";
|
|
32
|
-
readonly reportMode: "
|
|
35
|
+
readonly reportMode: "summary-first";
|
|
33
36
|
readonly treatInfoAsWarning: true;
|
|
34
37
|
readonly includeIssues: true;
|
|
35
38
|
};
|
package/dist/tool-guidance.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ZodError } from "zod";
|
|
2
2
|
import { buildSuggestedCall } from "./build-suggested-call.js";
|
|
3
3
|
import { ERROR_CODES, isAppError } from "./errors.js";
|
|
4
|
-
import { statusForErrorCode } from "./error-mapping.js";
|
|
4
|
+
import { extractAllowlistedContext, issueOriginForErrorCode, retryClassForErrorCode, statusForErrorCode } from "./error-mapping.js";
|
|
5
5
|
export const SUGGESTED_CALL_DEFAULTS = {
|
|
6
6
|
allowDecompile: true,
|
|
7
7
|
preferProjectVersion: false,
|
|
@@ -14,7 +14,7 @@ export const SUGGESTED_CALL_DEFAULTS = {
|
|
|
14
14
|
explain: false,
|
|
15
15
|
preferProjectMapping: false,
|
|
16
16
|
minSeverity: "all",
|
|
17
|
-
reportMode: "
|
|
17
|
+
reportMode: "summary-first",
|
|
18
18
|
treatInfoAsWarning: true,
|
|
19
19
|
includeIssues: true
|
|
20
20
|
};
|
|
@@ -98,14 +98,19 @@ export function extractValidatedSuggestionAndExamples(details) {
|
|
|
98
98
|
typeof ex.reason !== "string") {
|
|
99
99
|
continue;
|
|
100
100
|
}
|
|
101
|
+
// Re-validate through the example path, not the primary path: example
|
|
102
|
+
// calls are templates and MAY carry placeholder sentinels (e.g.
|
|
103
|
+
// "<version>"), which the primary gate intentionally rejects.
|
|
101
104
|
const result = buildSuggestedCall({
|
|
102
105
|
tool: ex.tool,
|
|
103
|
-
params:
|
|
106
|
+
params: undefined,
|
|
107
|
+
examples: [{ params: ex.params, reason: ex.reason }]
|
|
104
108
|
});
|
|
105
|
-
|
|
109
|
+
const validatedExample = result.exampleCalls?.[0];
|
|
110
|
+
if (validatedExample) {
|
|
106
111
|
validated.push({
|
|
107
112
|
tool: ex.tool,
|
|
108
|
-
params:
|
|
113
|
+
params: validatedExample.params,
|
|
109
114
|
reason: ex.reason
|
|
110
115
|
});
|
|
111
116
|
}
|
|
@@ -414,12 +419,10 @@ export function buildSourceLookupSuggestedParams(tool, normalizedInput) {
|
|
|
414
419
|
const targetValue = asNonEmptyString(record?.target);
|
|
415
420
|
result.target = targetValue
|
|
416
421
|
? {
|
|
417
|
-
type: "resolve",
|
|
418
422
|
kind: inferTargetKindFromString(targetValue),
|
|
419
423
|
value: targetValue
|
|
420
424
|
}
|
|
421
425
|
: {
|
|
422
|
-
type: "resolve",
|
|
423
426
|
kind: "version",
|
|
424
427
|
value: "<minecraft-version>"
|
|
425
428
|
};
|
|
@@ -607,7 +610,21 @@ export function buildValidateProjectSuggestedParams(normalizedInput) {
|
|
|
607
610
|
return result;
|
|
608
611
|
}
|
|
609
612
|
function gatedGuidance(tool, hints, params) {
|
|
610
|
-
|
|
613
|
+
// Pass the reconstructed params as both the primary and a fallback example.
|
|
614
|
+
// When they are fully executable they become `suggestedCall`; when they still
|
|
615
|
+
// carry `<…>` placeholders (e.g. a missing version) the gate drops the
|
|
616
|
+
// primary and surfaces the same shape as an `exampleCalls` template instead
|
|
617
|
+
// of emitting a non-callable suggestedCall.
|
|
618
|
+
const validated = buildSuggestedCall({
|
|
619
|
+
tool,
|
|
620
|
+
params,
|
|
621
|
+
examples: [
|
|
622
|
+
{
|
|
623
|
+
params,
|
|
624
|
+
reason: `Example ${tool} call shape — replace any <…> placeholder values before sending.`
|
|
625
|
+
}
|
|
626
|
+
]
|
|
627
|
+
});
|
|
611
628
|
return {
|
|
612
629
|
hints,
|
|
613
630
|
...validated,
|
|
@@ -630,8 +647,8 @@ export function buildInvalidInputGuidance(tool, normalizedInput) {
|
|
|
630
647
|
}
|
|
631
648
|
if (tool === "get-class-source" || tool === "get-class-members") {
|
|
632
649
|
return gatedGuidance(tool, [
|
|
633
|
-
`${tool}.target must be an object: {"
|
|
634
|
-
"Bare string targets are not accepted; wrap the value under target.
|
|
650
|
+
`${tool}.target must be an object: {"kind":"version|jar|coordinate","value":"..."} or {"kind":"artifact","artifactId":"..."} (same shape as resolve-artifact).`,
|
|
651
|
+
"Bare string targets are not accepted; wrap the value under target.kind/target.value."
|
|
635
652
|
], buildSourceLookupSuggestedParams(tool, normalizedInput));
|
|
636
653
|
}
|
|
637
654
|
if (tool === "validate-project") {
|
|
@@ -666,6 +683,8 @@ export function mapErrorToProblem(caughtError, requestId, context) {
|
|
|
666
683
|
status: 400,
|
|
667
684
|
code: ERROR_CODES.INVALID_INPUT,
|
|
668
685
|
instance: requestId,
|
|
686
|
+
retryClass: retryClassForErrorCode(ERROR_CODES.INVALID_INPUT),
|
|
687
|
+
issueOrigin: issueOriginForErrorCode(ERROR_CODES.INVALID_INPUT),
|
|
669
688
|
fieldErrors: toFieldErrorsFromZod(caughtError),
|
|
670
689
|
hints: hintsWithFallback,
|
|
671
690
|
...(guidance?.suggestedCall ? { suggestedCall: guidance.suggestedCall } : {}),
|
|
@@ -675,6 +694,7 @@ export function mapErrorToProblem(caughtError, requestId, context) {
|
|
|
675
694
|
}
|
|
676
695
|
if (isAppError(caughtError)) {
|
|
677
696
|
const { suggestedCall, exampleCalls, primaryDropped } = extractValidatedSuggestionAndExamples(caughtError.details);
|
|
697
|
+
const sanitizedContext = extractAllowlistedContext(caughtError.details);
|
|
678
698
|
let failedStage = extractFailedStageFromDetails(caughtError.details);
|
|
679
699
|
if (!failedStage
|
|
680
700
|
&& context?.tool === "validate-mixin"
|
|
@@ -692,11 +712,14 @@ export function mapErrorToProblem(caughtError, requestId, context) {
|
|
|
692
712
|
status: statusForErrorCode(caughtError.code),
|
|
693
713
|
code: caughtError.code,
|
|
694
714
|
instance: requestId,
|
|
715
|
+
retryClass: retryClassForErrorCode(caughtError.code),
|
|
716
|
+
issueOrigin: issueOriginForErrorCode(caughtError.code),
|
|
695
717
|
fieldErrors: extractFieldErrorsFromDetails(caughtError.details),
|
|
696
718
|
hints: hintsWithFallback,
|
|
697
719
|
...(suggestedCall ? { suggestedCall } : {}),
|
|
698
720
|
...(exampleCalls ? { exampleCalls } : {}),
|
|
699
|
-
...(failedStage ? { failedStage } : {})
|
|
721
|
+
...(failedStage ? { failedStage } : {}),
|
|
722
|
+
...(sanitizedContext ? { context: sanitizedContext } : {})
|
|
700
723
|
};
|
|
701
724
|
}
|
|
702
725
|
return {
|
|
@@ -705,7 +728,9 @@ export function mapErrorToProblem(caughtError, requestId, context) {
|
|
|
705
728
|
detail: "Unexpected server error.",
|
|
706
729
|
status: 500,
|
|
707
730
|
code: ERROR_CODES.INTERNAL,
|
|
708
|
-
instance: requestId
|
|
731
|
+
instance: requestId,
|
|
732
|
+
retryClass: retryClassForErrorCode(ERROR_CODES.INTERNAL),
|
|
733
|
+
issueOrigin: issueOriginForErrorCode(ERROR_CODES.INTERNAL)
|
|
709
734
|
};
|
|
710
735
|
}
|
|
711
736
|
/**
|