@adhisang/minecraft-modding-mcp 3.0.0 → 3.1.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.
- package/CHANGELOG.md +52 -29
- package/README.md +209 -849
- package/dist/config.js +19 -11
- package/dist/entry-tools/analyze-mod-service.d.ts +16 -16
- package/dist/entry-tools/analyze-mod-service.js +69 -13
- package/dist/entry-tools/analyze-symbol-service.d.ts +14 -12
- package/dist/entry-tools/analyze-symbol-service.js +64 -6
- package/dist/entry-tools/compare-minecraft-service.d.ts +6 -6
- package/dist/entry-tools/compare-minecraft-service.js +58 -26
- package/dist/entry-tools/inspect-minecraft-service.d.ts +37 -19
- package/dist/entry-tools/inspect-minecraft-service.js +468 -51
- package/dist/entry-tools/manage-cache-service.d.ts +6 -6
- package/dist/entry-tools/manage-cache-service.js +40 -5
- package/dist/entry-tools/response-contract.d.ts +1 -0
- package/dist/entry-tools/response-contract.js +3 -0
- package/dist/entry-tools/validate-project-service.d.ts +24 -24
- package/dist/entry-tools/validate-project-service.js +40 -7
- package/dist/index.js +290 -51
- package/dist/mapping-service.d.ts +1 -0
- package/dist/mapping-service.js +55 -34
- package/dist/observability.d.ts +18 -2
- package/dist/observability.js +47 -10
- package/dist/source-service.d.ts +2 -1
- package/dist/source-service.js +206 -112
- package/dist/storage/files-repo.d.ts +1 -0
- package/dist/storage/files-repo.js +29 -5
- package/dist/tool-contract-manifest.d.ts +4 -0
- package/dist/tool-contract-manifest.js +137 -0
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { createError, ERROR_CODES } from "../errors.js";
|
|
2
|
+
import { createError, ERROR_CODES, isAppError } from "../errors.js";
|
|
3
3
|
import { buildIncludeSchema, detailSchema, positiveIntSchema } from "./entry-tool-schema.js";
|
|
4
|
-
import { buildEntryToolResult, buildEntryToolMeta, createNextAction, createTruncationMeta } from "./response-contract.js";
|
|
4
|
+
import { buildEntryToolResult, buildEntryToolMeta, createNextAction, createSummarySubject, createTruncationMeta } from "./response-contract.js";
|
|
5
5
|
import { capArray, nextActionsOrUndefined, resolveDetail, resolveInclude } from "./request-normalizers.js";
|
|
6
6
|
const INCLUDE_GROUPS = ["warnings", "provenance", "candidates", "members", "source", "files", "samples", "artifact", "timings"];
|
|
7
7
|
const TASKS = ["auto", "versions", "artifact", "class-overview", "class-source", "class-members", "search", "file", "list-files"];
|
|
@@ -41,7 +41,7 @@ const workspaceFocusSchema = z.discriminatedUnion("kind", [
|
|
|
41
41
|
symbolKind: z.enum(["class", "interface", "enum", "record", "method", "field"]).optional(),
|
|
42
42
|
packagePrefix: nonEmptyString.optional(),
|
|
43
43
|
fileGlob: nonEmptyString.optional(),
|
|
44
|
-
queryMode: z.enum(["auto", "token", "literal"]).
|
|
44
|
+
queryMode: z.enum(["auto", "token", "literal"]).default("auto")
|
|
45
45
|
})
|
|
46
46
|
]);
|
|
47
47
|
const subjectSchema = z.discriminatedUnion("kind", [
|
|
@@ -87,7 +87,7 @@ const subjectSchema = z.discriminatedUnion("kind", [
|
|
|
87
87
|
symbolKind: z.enum(["class", "interface", "enum", "record", "method", "field"]).optional(),
|
|
88
88
|
packagePrefix: nonEmptyString.optional(),
|
|
89
89
|
fileGlob: nonEmptyString.optional(),
|
|
90
|
-
queryMode: z.enum(["auto", "token", "literal"]).
|
|
90
|
+
queryMode: z.enum(["auto", "token", "literal"]).default("auto")
|
|
91
91
|
}),
|
|
92
92
|
z.object({
|
|
93
93
|
kind: z.literal("workspace"),
|
|
@@ -102,7 +102,7 @@ const subjectSchema = z.discriminatedUnion("kind", [
|
|
|
102
102
|
export const inspectMinecraftShape = {
|
|
103
103
|
task: z.enum(TASKS).optional(),
|
|
104
104
|
subject: subjectSchema.optional(),
|
|
105
|
-
includeSnapshots: z.boolean().
|
|
105
|
+
includeSnapshots: z.boolean().default(false),
|
|
106
106
|
detail: detailSchema.optional(),
|
|
107
107
|
include: buildIncludeSchema(INCLUDE_GROUPS),
|
|
108
108
|
limit: positiveIntSchema.optional(),
|
|
@@ -116,7 +116,7 @@ export const inspectMinecraftSchema = z.object(inspectMinecraftShape).superRefin
|
|
|
116
116
|
message: "subject is required unless task=versions."
|
|
117
117
|
});
|
|
118
118
|
}
|
|
119
|
-
if (value.includeSnapshots
|
|
119
|
+
if (value.includeSnapshots && value.task && value.task !== "versions") {
|
|
120
120
|
ctx.addIssue({
|
|
121
121
|
code: z.ZodIssueCode.custom,
|
|
122
122
|
path: ["includeSnapshots"],
|
|
@@ -124,6 +124,32 @@ export const inspectMinecraftSchema = z.object(inspectMinecraftShape).superRefin
|
|
|
124
124
|
});
|
|
125
125
|
}
|
|
126
126
|
});
|
|
127
|
+
function hasPartialVanillaCoverage(artifact) {
|
|
128
|
+
return artifact?.qualityFlags.includes("partial-source-no-net-minecraft") === true
|
|
129
|
+
|| artifact?.artifactContents.sourceCoverage === "partial";
|
|
130
|
+
}
|
|
131
|
+
function looksLikeClassQuery(query) {
|
|
132
|
+
const trimmed = query.trim();
|
|
133
|
+
if (!/^[A-Za-z_$][A-Za-z0-9_$.]*$/.test(trimmed)) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
const simpleName = trimmed.split(".").at(-1) ?? trimmed;
|
|
137
|
+
return /^[A-Z_$]/.test(simpleName) || /^class_\d+(?:\$class_\d+)*$/.test(simpleName);
|
|
138
|
+
}
|
|
139
|
+
function classNameToFilePath(className) {
|
|
140
|
+
const topLevelClassName = className.split("$")[0] ?? className;
|
|
141
|
+
return `${topLevelClassName.replace(/\./g, "/")}.java`;
|
|
142
|
+
}
|
|
143
|
+
function isVanillaNamespacePath(filePath) {
|
|
144
|
+
return filePath.startsWith("net/minecraft/") || filePath.startsWith("com/mojang/");
|
|
145
|
+
}
|
|
146
|
+
function hitTargetsVanillaNamespace(hit) {
|
|
147
|
+
if (isVanillaNamespacePath(hit.filePath)) {
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
const qualifiedName = hit.symbol?.qualifiedName;
|
|
151
|
+
return qualifiedName?.startsWith("net.minecraft.") === true || qualifiedName?.startsWith("com.mojang.") === true;
|
|
152
|
+
}
|
|
127
153
|
export class InspectMinecraftService {
|
|
128
154
|
deps;
|
|
129
155
|
constructor(deps) {
|
|
@@ -200,11 +226,11 @@ export class InspectMinecraftService {
|
|
|
200
226
|
strictVersion: subject.strictVersion
|
|
201
227
|
};
|
|
202
228
|
}
|
|
203
|
-
async resolveClassArtifactReference(subject, classSubject) {
|
|
229
|
+
async resolveClassArtifactReference(subject, classSubject, task) {
|
|
204
230
|
if (subject.kind === "workspace") {
|
|
205
231
|
return this.resolveWorkspaceArtifactReference(subject, classSubject.artifact);
|
|
206
232
|
}
|
|
207
|
-
return this.resolveArtifactReference(classSubject);
|
|
233
|
+
return this.resolveArtifactReference(classSubject, task);
|
|
208
234
|
}
|
|
209
235
|
async resolveWorkspaceArtifactReference(subject, artifactRef) {
|
|
210
236
|
if (!artifactRef) {
|
|
@@ -260,15 +286,150 @@ export class InspectMinecraftService {
|
|
|
260
286
|
return "search";
|
|
261
287
|
}
|
|
262
288
|
}
|
|
263
|
-
|
|
289
|
+
summarizeRequestedSubject(subject) {
|
|
290
|
+
if (subject.kind === "search") {
|
|
291
|
+
if (subject.queryMode !== "auto") {
|
|
292
|
+
return subject;
|
|
293
|
+
}
|
|
294
|
+
const { queryMode: _queryMode, ...requestedSubject } = subject;
|
|
295
|
+
return requestedSubject;
|
|
296
|
+
}
|
|
297
|
+
if (subject.kind === "workspace" && subject.focus?.kind === "search") {
|
|
298
|
+
if (subject.focus.queryMode !== "auto") {
|
|
299
|
+
return subject;
|
|
300
|
+
}
|
|
301
|
+
const { queryMode: _queryMode, ...requestedFocus } = subject.focus;
|
|
302
|
+
return {
|
|
303
|
+
...subject,
|
|
304
|
+
focus: requestedFocus
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
return subject;
|
|
308
|
+
}
|
|
309
|
+
async exampleVersionForSubject(subject) {
|
|
310
|
+
if ("projectPath" in subject && typeof subject.projectPath === "string") {
|
|
311
|
+
const detectedVersion = await this.deps.detectProjectMinecraftVersion(subject.projectPath);
|
|
312
|
+
if (detectedVersion) {
|
|
313
|
+
return detectedVersion;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return "<version>";
|
|
317
|
+
}
|
|
318
|
+
async buildArtifactContextSuggestedCall(task, subject) {
|
|
319
|
+
return {
|
|
320
|
+
tool: "inspect-minecraft",
|
|
321
|
+
params: {
|
|
322
|
+
task,
|
|
323
|
+
subject: {
|
|
324
|
+
...subject,
|
|
325
|
+
artifact: {
|
|
326
|
+
type: "resolve-target",
|
|
327
|
+
target: {
|
|
328
|
+
kind: "version",
|
|
329
|
+
value: await this.exampleVersionForSubject(subject)
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
taskForSubject(subject) {
|
|
337
|
+
return this.resolveTask(undefined, subject);
|
|
338
|
+
}
|
|
339
|
+
invalidTaskSubjectError(task, subject) {
|
|
340
|
+
if (task === "class-source" && subject.kind === "version") {
|
|
341
|
+
throw createError({
|
|
342
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
343
|
+
message: "class-source requires a class subject; version subjects resolve artifacts, not class names.",
|
|
344
|
+
details: {
|
|
345
|
+
nextAction: "Retry class-source with subject.kind=class and attach artifact context, or use task=artifact to inspect the version first.",
|
|
346
|
+
suggestedCall: {
|
|
347
|
+
tool: "inspect-minecraft",
|
|
348
|
+
params: {
|
|
349
|
+
task: "class-source",
|
|
350
|
+
subject: {
|
|
351
|
+
kind: "class",
|
|
352
|
+
className: "net.minecraft.world.item.Item",
|
|
353
|
+
artifact: {
|
|
354
|
+
type: "resolve-target",
|
|
355
|
+
target: {
|
|
356
|
+
kind: "version",
|
|
357
|
+
value: subject.version
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
const suggestedTask = this.taskForSubject(subject);
|
|
367
|
+
throw createError({
|
|
368
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
369
|
+
message: `${task} is not compatible with subject.kind="${subject.kind}".`,
|
|
370
|
+
details: {
|
|
371
|
+
nextAction: suggestedTask === "artifact"
|
|
372
|
+
? `Retry with task=artifact for this ${subject.kind} subject, or reshape the subject so it supplies the input that ${task} needs.`
|
|
373
|
+
: `Retry with task=${suggestedTask} for this subject, or reshape the subject so it supplies the input that ${task} needs.`,
|
|
374
|
+
suggestedCall: {
|
|
375
|
+
tool: "inspect-minecraft",
|
|
376
|
+
params: {
|
|
377
|
+
task: suggestedTask,
|
|
378
|
+
subject
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
async resolveBinaryBackedClass(className, input) {
|
|
385
|
+
if (!this.deps.checkSymbolExists || !input.version) {
|
|
386
|
+
return undefined;
|
|
387
|
+
}
|
|
388
|
+
let lookup;
|
|
389
|
+
try {
|
|
390
|
+
lookup = await this.deps.checkSymbolExists({
|
|
391
|
+
version: input.version,
|
|
392
|
+
kind: "class",
|
|
393
|
+
name: className,
|
|
394
|
+
sourceMapping: input.mapping ?? "obfuscated",
|
|
395
|
+
nameMode: className.includes(".") ? "fqcn" : "auto",
|
|
396
|
+
maxCandidates: 10
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
catch (caughtError) {
|
|
400
|
+
if (isAppError(caughtError)) {
|
|
401
|
+
return undefined;
|
|
402
|
+
}
|
|
403
|
+
throw caughtError;
|
|
404
|
+
}
|
|
405
|
+
const resolvedClassName = lookup.resolvedSymbol?.name;
|
|
406
|
+
if (!resolvedClassName) {
|
|
407
|
+
return undefined;
|
|
408
|
+
}
|
|
409
|
+
return {
|
|
410
|
+
className: resolvedClassName,
|
|
411
|
+
warnings: lookup.warnings
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
async resolveArtifactReference(subject, task) {
|
|
264
415
|
if (subject.kind === "artifact") {
|
|
265
416
|
return this.resolveArtifactRef(subject.artifact, subject);
|
|
266
417
|
}
|
|
267
418
|
if (subject.kind === "class" || subject.kind === "file" || subject.kind === "search") {
|
|
268
419
|
if (!subject.artifact) {
|
|
420
|
+
const suggestedTask = task
|
|
421
|
+
?? (subject.kind === "class"
|
|
422
|
+
? "class-overview"
|
|
423
|
+
: subject.kind === "search"
|
|
424
|
+
? "search"
|
|
425
|
+
: "file");
|
|
269
426
|
throw createError({
|
|
270
427
|
code: ERROR_CODES.INVALID_INPUT,
|
|
271
|
-
message: `${subject.kind} subject requires artifact context
|
|
428
|
+
message: `${subject.kind} subject requires artifact context.`,
|
|
429
|
+
details: {
|
|
430
|
+
nextAction: "Add subject.artifact or use subject.kind=workspace so inspect-minecraft can resolve the artifact first.",
|
|
431
|
+
suggestedCall: await this.buildArtifactContextSuggestedCall(suggestedTask, subject)
|
|
432
|
+
}
|
|
272
433
|
});
|
|
273
434
|
}
|
|
274
435
|
return this.resolveArtifactRef(subject.artifact, subject);
|
|
@@ -341,6 +502,12 @@ export class InspectMinecraftService {
|
|
|
341
502
|
const summary = {
|
|
342
503
|
status: "ok",
|
|
343
504
|
headline: `Found ${versions.totalAvailable} Minecraft versions.`,
|
|
505
|
+
subject: createSummarySubject({
|
|
506
|
+
task: "versions",
|
|
507
|
+
kind: "versions",
|
|
508
|
+
includeSnapshots: input.includeSnapshots === false ? undefined : input.includeSnapshots,
|
|
509
|
+
limit: input.limit
|
|
510
|
+
}),
|
|
344
511
|
counts: {
|
|
345
512
|
releases: versions.releases.length,
|
|
346
513
|
snapshots: versions.snapshots?.length ?? 0
|
|
@@ -379,6 +546,10 @@ export class InspectMinecraftService {
|
|
|
379
546
|
const summary = {
|
|
380
547
|
status: "blocked",
|
|
381
548
|
headline: "Could not resolve an artifact without a Minecraft version.",
|
|
549
|
+
subject: createSummarySubject({
|
|
550
|
+
task: "artifact",
|
|
551
|
+
requested: subject
|
|
552
|
+
}),
|
|
382
553
|
nextActions: nextActionsOrUndefined([
|
|
383
554
|
createNextAction("inspect-minecraft", {
|
|
384
555
|
task: "artifact",
|
|
@@ -408,6 +579,12 @@ export class InspectMinecraftService {
|
|
|
408
579
|
const summary = {
|
|
409
580
|
status: "ok",
|
|
410
581
|
headline: `Resolved artifact ${resolved.artifactId}.`,
|
|
582
|
+
subject: createSummarySubject({
|
|
583
|
+
task: "artifact",
|
|
584
|
+
requested: subject,
|
|
585
|
+
artifactId: resolved.artifactId,
|
|
586
|
+
version: resolved.version
|
|
587
|
+
}),
|
|
411
588
|
counts: {
|
|
412
589
|
warnings: resolved.warnings.length
|
|
413
590
|
}
|
|
@@ -443,18 +620,20 @@ export class InspectMinecraftService {
|
|
|
443
620
|
}
|
|
444
621
|
async handleClassOverview(subject, detail, include) {
|
|
445
622
|
if (subject.kind !== "class" && !(subject.kind === "workspace" && subject.focus?.kind === "class")) {
|
|
446
|
-
|
|
447
|
-
code: ERROR_CODES.INVALID_INPUT,
|
|
448
|
-
message: "class-overview requires a class or workspace focus subject."
|
|
449
|
-
});
|
|
623
|
+
this.invalidTaskSubjectError("class-overview", subject);
|
|
450
624
|
}
|
|
451
625
|
const classSubject = this.buildClassSubject(subject);
|
|
452
626
|
const className = classSubject.className;
|
|
453
|
-
const artifact = await this.resolveClassArtifactReference(subject, classSubject);
|
|
627
|
+
const artifact = await this.resolveClassArtifactReference(subject, classSubject, "class-overview");
|
|
454
628
|
if (!artifact.artifactId) {
|
|
455
629
|
const summary = {
|
|
456
630
|
status: "blocked",
|
|
457
|
-
headline: `Could not resolve artifact context for ${className}
|
|
631
|
+
headline: `Could not resolve artifact context for ${className}.`,
|
|
632
|
+
subject: createSummarySubject({
|
|
633
|
+
task: "class-overview",
|
|
634
|
+
requested: subject,
|
|
635
|
+
className
|
|
636
|
+
})
|
|
458
637
|
};
|
|
459
638
|
return {
|
|
460
639
|
...buildEntryToolResult({
|
|
@@ -478,9 +657,78 @@ export class InspectMinecraftService {
|
|
|
478
657
|
limit: 10
|
|
479
658
|
});
|
|
480
659
|
if (matches.total === 0) {
|
|
660
|
+
const partialSourceFallback = subject.kind === "workspace" && hasPartialVanillaCoverage(artifact.artifact)
|
|
661
|
+
? await this.resolveBinaryBackedClass(className, {
|
|
662
|
+
version: artifact.version,
|
|
663
|
+
mapping: classSubject.mapping
|
|
664
|
+
})
|
|
665
|
+
: undefined;
|
|
666
|
+
if (partialSourceFallback) {
|
|
667
|
+
const metadata = await this.deps.getClassSource({
|
|
668
|
+
className: partialSourceFallback.className,
|
|
669
|
+
artifactId: artifact.artifactId,
|
|
670
|
+
mapping: classSubject.mapping,
|
|
671
|
+
scope: classSubject.scope,
|
|
672
|
+
projectPath: classSubject.projectPath,
|
|
673
|
+
preferProjectVersion: classSubject.preferProjectVersion,
|
|
674
|
+
strictVersion: classSubject.strictVersion,
|
|
675
|
+
mode: "metadata"
|
|
676
|
+
});
|
|
677
|
+
const summary = {
|
|
678
|
+
status: "ok",
|
|
679
|
+
headline: `Resolved class overview for ${partialSourceFallback.className}.`,
|
|
680
|
+
subject: createSummarySubject({
|
|
681
|
+
task: "class-overview",
|
|
682
|
+
requested: subject,
|
|
683
|
+
className: partialSourceFallback.className,
|
|
684
|
+
artifactId: metadata.artifactId
|
|
685
|
+
}),
|
|
686
|
+
counts: {
|
|
687
|
+
totalLines: metadata.totalLines
|
|
688
|
+
},
|
|
689
|
+
notes: [
|
|
690
|
+
"Source coverage was partial, so inspect-minecraft confirmed the vanilla class through binary-backed symbol lookup."
|
|
691
|
+
]
|
|
692
|
+
};
|
|
693
|
+
return {
|
|
694
|
+
...buildEntryToolResult({
|
|
695
|
+
task: "class-overview",
|
|
696
|
+
summary,
|
|
697
|
+
detail,
|
|
698
|
+
include,
|
|
699
|
+
blocks: {
|
|
700
|
+
subject: {
|
|
701
|
+
requested: subject,
|
|
702
|
+
resolved: {
|
|
703
|
+
artifactId: metadata.artifactId,
|
|
704
|
+
className: partialSourceFallback.className
|
|
705
|
+
}
|
|
706
|
+
},
|
|
707
|
+
class: {
|
|
708
|
+
className: partialSourceFallback.className,
|
|
709
|
+
totalLines: metadata.totalLines,
|
|
710
|
+
returnedNamespace: metadata.returnedNamespace
|
|
711
|
+
}
|
|
712
|
+
},
|
|
713
|
+
alwaysBlocks: ["subject"]
|
|
714
|
+
}),
|
|
715
|
+
warnings: [
|
|
716
|
+
...artifact.warnings,
|
|
717
|
+
...matches.warnings,
|
|
718
|
+
...partialSourceFallback.warnings,
|
|
719
|
+
...metadata.warnings
|
|
720
|
+
]
|
|
721
|
+
};
|
|
722
|
+
}
|
|
481
723
|
const summary = {
|
|
482
724
|
status: "not_found",
|
|
483
|
-
headline: `No class match was found for ${className}
|
|
725
|
+
headline: `No class match was found for ${className}.`,
|
|
726
|
+
subject: createSummarySubject({
|
|
727
|
+
task: "class-overview",
|
|
728
|
+
requested: subject,
|
|
729
|
+
className,
|
|
730
|
+
artifactId: artifact.artifactId
|
|
731
|
+
})
|
|
484
732
|
};
|
|
485
733
|
return {
|
|
486
734
|
...buildEntryToolResult({
|
|
@@ -517,6 +765,12 @@ export class InspectMinecraftService {
|
|
|
517
765
|
const summary = {
|
|
518
766
|
status: "ambiguous",
|
|
519
767
|
headline: `Found ${matches.total} class matches for ${className}.`,
|
|
768
|
+
subject: createSummarySubject({
|
|
769
|
+
task: "class-overview",
|
|
770
|
+
requested: subject,
|
|
771
|
+
className,
|
|
772
|
+
artifactId: artifact.artifactId
|
|
773
|
+
}),
|
|
520
774
|
counts: {
|
|
521
775
|
matches: matches.total
|
|
522
776
|
},
|
|
@@ -551,6 +805,12 @@ export class InspectMinecraftService {
|
|
|
551
805
|
const summary = {
|
|
552
806
|
status: "ok",
|
|
553
807
|
headline: `Resolved class overview for ${match.qualifiedName}.`,
|
|
808
|
+
subject: createSummarySubject({
|
|
809
|
+
task: "class-overview",
|
|
810
|
+
requested: subject,
|
|
811
|
+
className: match.qualifiedName,
|
|
812
|
+
artifactId: artifact.artifactId
|
|
813
|
+
}),
|
|
554
814
|
counts: {
|
|
555
815
|
totalLines: metadata.totalLines
|
|
556
816
|
},
|
|
@@ -604,14 +864,11 @@ export class InspectMinecraftService {
|
|
|
604
864
|
}
|
|
605
865
|
async handleClassSource(subject, detail, include) {
|
|
606
866
|
if (subject.kind !== "class" && !(subject.kind === "workspace" && subject.focus?.kind === "class")) {
|
|
607
|
-
|
|
608
|
-
code: ERROR_CODES.INVALID_INPUT,
|
|
609
|
-
message: "class-source requires a class or workspace focus subject."
|
|
610
|
-
});
|
|
867
|
+
this.invalidTaskSubjectError("class-source", subject);
|
|
611
868
|
}
|
|
612
869
|
const classSubject = this.buildClassSubject(subject);
|
|
613
870
|
const className = classSubject.className;
|
|
614
|
-
const artifactContext = await this.resolveClassArtifactReference(subject, classSubject);
|
|
871
|
+
const artifactContext = await this.resolveClassArtifactReference(subject, classSubject, "class-source");
|
|
615
872
|
const source = await this.deps.getClassSource({
|
|
616
873
|
className,
|
|
617
874
|
artifactId: artifactContext.artifactId || undefined,
|
|
@@ -625,6 +882,12 @@ export class InspectMinecraftService {
|
|
|
625
882
|
const summary = {
|
|
626
883
|
status: "ok",
|
|
627
884
|
headline: `Resolved source for ${source.className}.`,
|
|
885
|
+
subject: createSummarySubject({
|
|
886
|
+
task: "class-source",
|
|
887
|
+
requested: subject,
|
|
888
|
+
className: source.className,
|
|
889
|
+
artifactId: source.artifactId
|
|
890
|
+
}),
|
|
628
891
|
counts: {
|
|
629
892
|
totalLines: source.totalLines
|
|
630
893
|
}
|
|
@@ -658,13 +921,10 @@ export class InspectMinecraftService {
|
|
|
658
921
|
}
|
|
659
922
|
async handleClassMembers(subject, detail, include, limit) {
|
|
660
923
|
if (subject.kind !== "class" && !(subject.kind === "workspace" && subject.focus?.kind === "class")) {
|
|
661
|
-
|
|
662
|
-
code: ERROR_CODES.INVALID_INPUT,
|
|
663
|
-
message: "class-members requires a class or workspace focus subject."
|
|
664
|
-
});
|
|
924
|
+
this.invalidTaskSubjectError("class-members", subject);
|
|
665
925
|
}
|
|
666
926
|
const classSubject = this.buildClassSubject(subject);
|
|
667
|
-
const artifact = await this.resolveClassArtifactReference(subject, classSubject);
|
|
927
|
+
const artifact = await this.resolveClassArtifactReference(subject, classSubject, "class-members");
|
|
668
928
|
const members = await this.deps.getClassMembers({
|
|
669
929
|
className: classSubject.className,
|
|
670
930
|
artifactId: artifact.artifactId || undefined,
|
|
@@ -678,6 +938,12 @@ export class InspectMinecraftService {
|
|
|
678
938
|
const summary = {
|
|
679
939
|
status: members.truncated ? "partial" : "ok",
|
|
680
940
|
headline: `Collected ${members.counts.total} members for ${members.className}.`,
|
|
941
|
+
subject: createSummarySubject({
|
|
942
|
+
task: "class-members",
|
|
943
|
+
requested: subject,
|
|
944
|
+
className: members.className,
|
|
945
|
+
artifactId: members.artifactId
|
|
946
|
+
}),
|
|
681
947
|
counts: members.counts
|
|
682
948
|
};
|
|
683
949
|
return {
|
|
@@ -727,21 +993,20 @@ export class InspectMinecraftService {
|
|
|
727
993
|
}
|
|
728
994
|
async handleSearch(subject, detail, include, limit, cursor) {
|
|
729
995
|
if (subject.kind !== "search" && !(subject.kind === "workspace" && subject.focus?.kind === "search")) {
|
|
730
|
-
|
|
731
|
-
code: ERROR_CODES.INVALID_INPUT,
|
|
732
|
-
message: "search requires a search or workspace focus subject."
|
|
733
|
-
});
|
|
996
|
+
this.invalidTaskSubjectError("search", subject);
|
|
734
997
|
}
|
|
735
998
|
const searchSubject = subject.kind === "search" ? subject : this.requireWorkspaceSearchFocus(subject);
|
|
999
|
+
const requestedSubject = this.summarizeRequestedSubject(subject);
|
|
1000
|
+
const queryMode = searchSubject.queryMode ?? "auto";
|
|
736
1001
|
const artifact = subject.kind === "search"
|
|
737
|
-
? await this.resolveArtifactReference(subject)
|
|
1002
|
+
? await this.resolveArtifactReference(subject, "search")
|
|
738
1003
|
: await this.resolveWorkspaceArtifactReference(subject, searchSubject.artifact);
|
|
739
1004
|
const search = await this.deps.searchClassSource({
|
|
740
1005
|
artifactId: artifact.artifactId,
|
|
741
1006
|
query: searchSubject.query,
|
|
742
1007
|
intent: searchSubject.intent,
|
|
743
1008
|
match: searchSubject.match,
|
|
744
|
-
queryMode
|
|
1009
|
+
queryMode,
|
|
745
1010
|
limit,
|
|
746
1011
|
cursor,
|
|
747
1012
|
scope: searchSubject.packagePrefix || searchSubject.fileGlob || searchSubject.symbolKind
|
|
@@ -752,15 +1017,106 @@ export class InspectMinecraftService {
|
|
|
752
1017
|
}
|
|
753
1018
|
: undefined
|
|
754
1019
|
});
|
|
755
|
-
const
|
|
1020
|
+
const needsBinaryBackedClassHit = subject.kind === "workspace" &&
|
|
1021
|
+
hasPartialVanillaCoverage(artifact.artifact) &&
|
|
1022
|
+
looksLikeClassQuery(searchSubject.query) &&
|
|
1023
|
+
!search.hits.some((hit) => hitTargetsVanillaNamespace(hit));
|
|
1024
|
+
const binaryBackedClassHit = needsBinaryBackedClassHit
|
|
1025
|
+
? await this.resolveBinaryBackedClass(searchSubject.query, {
|
|
1026
|
+
version: artifact.version,
|
|
1027
|
+
mapping: subject.mapping
|
|
1028
|
+
})
|
|
1029
|
+
: undefined;
|
|
1030
|
+
const binaryBackedHitRecord = binaryBackedClassHit == null
|
|
1031
|
+
? undefined
|
|
1032
|
+
: {
|
|
1033
|
+
filePath: classNameToFilePath(binaryBackedClassHit.className),
|
|
1034
|
+
score: 100,
|
|
1035
|
+
matchedIn: "symbol",
|
|
1036
|
+
reasonCodes: ["binary-class-lookup"],
|
|
1037
|
+
symbol: {
|
|
1038
|
+
symbolKind: "class",
|
|
1039
|
+
symbolName: binaryBackedClassHit.className.split(".").at(-1) ?? binaryBackedClassHit.className,
|
|
1040
|
+
qualifiedName: binaryBackedClassHit.className,
|
|
1041
|
+
line: 1
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
const effectiveHits = binaryBackedHitRecord == null
|
|
1045
|
+
? search.hits
|
|
1046
|
+
: [
|
|
1047
|
+
binaryBackedHitRecord,
|
|
1048
|
+
...search.hits.filter((hit) => hit.filePath !== binaryBackedHitRecord.filePath)
|
|
1049
|
+
];
|
|
1050
|
+
const sampledHits = capArray(effectiveHits, 5);
|
|
1051
|
+
const isAutoSeparatorMiss = effectiveHits.length === 0 &&
|
|
1052
|
+
queryMode === "auto" &&
|
|
1053
|
+
/[._$]/.test(searchSubject.query);
|
|
1054
|
+
const literalRetrySubject = subject.kind === "search"
|
|
1055
|
+
? {
|
|
1056
|
+
...subject,
|
|
1057
|
+
queryMode: "literal"
|
|
1058
|
+
}
|
|
1059
|
+
: {
|
|
1060
|
+
...subject,
|
|
1061
|
+
focus: {
|
|
1062
|
+
...searchSubject,
|
|
1063
|
+
kind: "search",
|
|
1064
|
+
queryMode: "literal"
|
|
1065
|
+
}
|
|
1066
|
+
};
|
|
756
1067
|
const summary = {
|
|
757
|
-
status:
|
|
758
|
-
headline:
|
|
759
|
-
? `Found ${
|
|
1068
|
+
status: effectiveHits.length > 0 ? "ok" : "not_found",
|
|
1069
|
+
headline: effectiveHits.length > 0
|
|
1070
|
+
? `Found ${effectiveHits.length} source hits for ${searchSubject.query}.`
|
|
760
1071
|
: `No source hits were found for ${searchSubject.query}.`,
|
|
1072
|
+
subject: createSummarySubject({
|
|
1073
|
+
task: "search",
|
|
1074
|
+
requested: requestedSubject,
|
|
1075
|
+
query: searchSubject.query,
|
|
1076
|
+
artifactId: artifact.artifactId
|
|
1077
|
+
}),
|
|
761
1078
|
counts: {
|
|
762
|
-
hits:
|
|
763
|
-
}
|
|
1079
|
+
hits: effectiveHits.length
|
|
1080
|
+
},
|
|
1081
|
+
nextActions: nextActionsOrUndefined([
|
|
1082
|
+
...(effectiveHits.length > 0
|
|
1083
|
+
? [
|
|
1084
|
+
createNextAction("inspect-minecraft", {
|
|
1085
|
+
task: "file",
|
|
1086
|
+
subject: {
|
|
1087
|
+
kind: "file",
|
|
1088
|
+
filePath: effectiveHits[0].filePath,
|
|
1089
|
+
artifact: {
|
|
1090
|
+
type: "resolved-id",
|
|
1091
|
+
artifactId: artifact.artifactId
|
|
1092
|
+
}
|
|
1093
|
+
},
|
|
1094
|
+
include: ["source"]
|
|
1095
|
+
})
|
|
1096
|
+
]
|
|
1097
|
+
: []),
|
|
1098
|
+
...(isAutoSeparatorMiss
|
|
1099
|
+
? [
|
|
1100
|
+
createNextAction("inspect-minecraft", {
|
|
1101
|
+
task: "search",
|
|
1102
|
+
subject: literalRetrySubject
|
|
1103
|
+
})
|
|
1104
|
+
]
|
|
1105
|
+
: [])
|
|
1106
|
+
]),
|
|
1107
|
+
...(isAutoSeparatorMiss
|
|
1108
|
+
? {
|
|
1109
|
+
notes: [
|
|
1110
|
+
"Separator query returned no indexed hits. Retry with queryMode=\"literal\" for an explicit full substring scan."
|
|
1111
|
+
]
|
|
1112
|
+
}
|
|
1113
|
+
: binaryBackedClassHit
|
|
1114
|
+
? {
|
|
1115
|
+
notes: [
|
|
1116
|
+
"Source coverage was partial, so inspect-minecraft returned a binary-backed class match for the vanilla symbol."
|
|
1117
|
+
]
|
|
1118
|
+
}
|
|
1119
|
+
: {})
|
|
764
1120
|
};
|
|
765
1121
|
return {
|
|
766
1122
|
...buildEntryToolResult({
|
|
@@ -770,32 +1126,29 @@ export class InspectMinecraftService {
|
|
|
770
1126
|
include,
|
|
771
1127
|
blocks: {
|
|
772
1128
|
subject: {
|
|
773
|
-
requested:
|
|
1129
|
+
requested: requestedSubject,
|
|
774
1130
|
resolved: {
|
|
775
1131
|
artifactId: artifact.artifactId
|
|
776
1132
|
}
|
|
777
1133
|
},
|
|
778
1134
|
search: {
|
|
779
1135
|
query: searchSubject.query,
|
|
780
|
-
hits: detail === "summary" ? sampledHits.items :
|
|
1136
|
+
hits: detail === "summary" ? sampledHits.items : effectiveHits,
|
|
781
1137
|
nextCursor: search.nextCursor
|
|
782
1138
|
}
|
|
783
1139
|
},
|
|
784
1140
|
alwaysBlocks: ["subject"]
|
|
785
1141
|
}),
|
|
786
|
-
warnings: [...artifact.warnings]
|
|
1142
|
+
warnings: [...artifact.warnings, ...(binaryBackedClassHit?.warnings ?? [])]
|
|
787
1143
|
};
|
|
788
1144
|
}
|
|
789
1145
|
async handleFile(subject, detail, include) {
|
|
790
1146
|
if (subject.kind !== "file" && !(subject.kind === "workspace" && subject.focus?.kind === "file")) {
|
|
791
|
-
|
|
792
|
-
code: ERROR_CODES.INVALID_INPUT,
|
|
793
|
-
message: "file task requires a file or workspace focus subject."
|
|
794
|
-
});
|
|
1147
|
+
this.invalidTaskSubjectError("file", subject);
|
|
795
1148
|
}
|
|
796
1149
|
const fileSubject = subject.kind === "file" ? subject : this.requireWorkspaceFileFocus(subject);
|
|
797
1150
|
const artifact = subject.kind === "file"
|
|
798
|
-
? await this.resolveArtifactReference(subject)
|
|
1151
|
+
? await this.resolveArtifactReference(subject, "file")
|
|
799
1152
|
: await this.resolveWorkspaceArtifactReference(subject, fileSubject.artifact);
|
|
800
1153
|
const file = await this.deps.getArtifactFile({
|
|
801
1154
|
artifactId: artifact.artifactId,
|
|
@@ -804,6 +1157,12 @@ export class InspectMinecraftService {
|
|
|
804
1157
|
const summary = {
|
|
805
1158
|
status: "ok",
|
|
806
1159
|
headline: `Read ${file.filePath}.`,
|
|
1160
|
+
subject: createSummarySubject({
|
|
1161
|
+
task: "file",
|
|
1162
|
+
requested: subject,
|
|
1163
|
+
filePath: file.filePath,
|
|
1164
|
+
artifactId: artifact.artifactId
|
|
1165
|
+
}),
|
|
807
1166
|
counts: {
|
|
808
1167
|
bytes: file.contentBytes
|
|
809
1168
|
}
|
|
@@ -835,19 +1194,69 @@ export class InspectMinecraftService {
|
|
|
835
1194
|
};
|
|
836
1195
|
}
|
|
837
1196
|
async handleListFiles(subject, detail, include, limit, cursor) {
|
|
838
|
-
const artifact = await this.resolveArtifactReference(subject);
|
|
1197
|
+
const artifact = await this.resolveArtifactReference(subject, "list-files");
|
|
839
1198
|
const files = await this.deps.listArtifactFiles({
|
|
840
1199
|
artifactId: artifact.artifactId,
|
|
841
1200
|
limit,
|
|
842
1201
|
cursor
|
|
843
1202
|
});
|
|
844
1203
|
const sampled = capArray(files.items, 10);
|
|
1204
|
+
const partialCoverage = subject.kind === "workspace" && hasPartialVanillaCoverage(artifact.artifact);
|
|
1205
|
+
const nextActions = [
|
|
1206
|
+
...(files.items.length > 0
|
|
1207
|
+
? [
|
|
1208
|
+
createNextAction("inspect-minecraft", {
|
|
1209
|
+
task: "file",
|
|
1210
|
+
subject: {
|
|
1211
|
+
kind: "file",
|
|
1212
|
+
filePath: files.items[0],
|
|
1213
|
+
artifact: {
|
|
1214
|
+
type: "resolved-id",
|
|
1215
|
+
artifactId: artifact.artifactId
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
})
|
|
1219
|
+
]
|
|
1220
|
+
: []),
|
|
1221
|
+
...(partialCoverage
|
|
1222
|
+
? [
|
|
1223
|
+
createNextAction("inspect-minecraft", {
|
|
1224
|
+
task: "class-source",
|
|
1225
|
+
subject: {
|
|
1226
|
+
kind: "workspace",
|
|
1227
|
+
projectPath: subject.projectPath,
|
|
1228
|
+
mapping: subject.mapping,
|
|
1229
|
+
scope: subject.scope,
|
|
1230
|
+
preferProjectVersion: subject.preferProjectVersion,
|
|
1231
|
+
strictVersion: subject.strictVersion,
|
|
1232
|
+
focus: {
|
|
1233
|
+
kind: "class",
|
|
1234
|
+
className: "net.minecraft.world.item.Item"
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
})
|
|
1238
|
+
]
|
|
1239
|
+
: [])
|
|
1240
|
+
];
|
|
845
1241
|
const summary = {
|
|
846
|
-
status: "ok",
|
|
1242
|
+
status: partialCoverage ? "partial" : "ok",
|
|
847
1243
|
headline: `Listed ${files.items.length} files for ${artifact.artifactId}.`,
|
|
1244
|
+
subject: createSummarySubject({
|
|
1245
|
+
task: "list-files",
|
|
1246
|
+
requested: subject,
|
|
1247
|
+
artifactId: artifact.artifactId
|
|
1248
|
+
}),
|
|
848
1249
|
counts: {
|
|
849
1250
|
files: files.items.length
|
|
850
|
-
}
|
|
1251
|
+
},
|
|
1252
|
+
nextActions: nextActionsOrUndefined(nextActions),
|
|
1253
|
+
...(partialCoverage
|
|
1254
|
+
? {
|
|
1255
|
+
notes: [
|
|
1256
|
+
"This listing is partial because the resolved source artifact does not contain net.minecraft entries."
|
|
1257
|
+
]
|
|
1258
|
+
}
|
|
1259
|
+
: {})
|
|
851
1260
|
};
|
|
852
1261
|
return {
|
|
853
1262
|
...buildEntryToolResult({
|
|
@@ -864,7 +1273,15 @@ export class InspectMinecraftService {
|
|
|
864
1273
|
},
|
|
865
1274
|
files: {
|
|
866
1275
|
items: detail === "summary" ? sampled.items : files.items,
|
|
867
|
-
nextCursor: files.nextCursor
|
|
1276
|
+
nextCursor: files.nextCursor,
|
|
1277
|
+
...(partialCoverage
|
|
1278
|
+
? {
|
|
1279
|
+
coverage: {
|
|
1280
|
+
sourceCoverage: "partial",
|
|
1281
|
+
missingNamespaces: ["net.minecraft"]
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
: {})
|
|
868
1285
|
}
|
|
869
1286
|
},
|
|
870
1287
|
alwaysBlocks: ["subject"]
|