@adhisang/minecraft-modding-mcp 4.2.1 → 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 +33 -0
- package/README.md +5 -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 +15 -1
- 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/index.js +138 -71
- 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 +6 -0
- 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/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.js +5 -2
- 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/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 +22 -17
- package/package.json +1 -1
|
@@ -5,7 +5,8 @@ import { ERROR_CODES, createError, isAppError } from "../errors.js";
|
|
|
5
5
|
import * as artifactResolver from "./artifact-resolver.js";
|
|
6
6
|
import * as classSourceHelpers from "./class-source-helpers.js";
|
|
7
7
|
import { buildClassSourceSnippet } from "./class-source/snippet-builder.js";
|
|
8
|
-
import { remapAndCountMembers, sliceMembersWithLimit } from "./class-source/members-builder.js";
|
|
8
|
+
import { remapAndCountMembers, sliceMembersWithLimit, projectMembersForWire } from "./class-source/members-builder.js";
|
|
9
|
+
import { buildPageContextKey, encodeOffsetCursor, resolveCursorOffset } from "../page-cursor.js";
|
|
9
10
|
import { dedupeQualityFlags, normalizeMapping, normalizeOptionalString, normalizePathStyle } from "./shared-utils.js";
|
|
10
11
|
const MEMBERS_STATUS_LEGACY = process.env.MEMBERS_STATUS_LEGACY === "1";
|
|
11
12
|
function normalizeStrictPositiveInt(value, field) {
|
|
@@ -257,6 +258,9 @@ export function buildDecompiledFallback(svc, artifactId, lookupClassName, member
|
|
|
257
258
|
}
|
|
258
259
|
};
|
|
259
260
|
}
|
|
261
|
+
// The class-like symbol kinds findClass returns. MUST stay in sync with the JS-side
|
|
262
|
+
// isTypeSymbol checks below; pushed down to SQL so non-type rows are never fetched.
|
|
263
|
+
const TYPE_SYMBOL_KINDS = ["class", "interface", "enum", "record"];
|
|
260
264
|
export function findClass(svc, input) {
|
|
261
265
|
const className = input.className.trim();
|
|
262
266
|
if (!className) {
|
|
@@ -278,12 +282,20 @@ export function findClass(svc, input) {
|
|
|
278
282
|
const warnings = [];
|
|
279
283
|
const isQualified = className.includes(".");
|
|
280
284
|
if (isQualified) {
|
|
281
|
-
|
|
285
|
+
// The innermost simple name (handles both dot- and $-separated inner types).
|
|
286
|
+
const simpleName = className.split(/[.$]/).at(-1) ?? className;
|
|
287
|
+
const directPath = `${classNameToClassPath(className)}.java`;
|
|
288
|
+
// The extractor stores ONE (outer) qualifiedName per file, so a nested type's
|
|
289
|
+
// FQCN never equals any stored qualifiedName and its synthetic file path
|
|
290
|
+
// (pkg/Outer/Inner.java) does not exist. resolveClassFilePath maps the FQCN —
|
|
291
|
+
// inner classes included — to the real outer file, which we match on.
|
|
292
|
+
const resolvedFilePath = resolveClassFilePath(svc, artifactId, className);
|
|
282
293
|
const result = svc.symbolsRepo.findScopedSymbols({
|
|
283
294
|
artifactId,
|
|
284
|
-
query:
|
|
295
|
+
query: simpleName,
|
|
285
296
|
match: "exact",
|
|
286
|
-
|
|
297
|
+
symbolKinds: TYPE_SYMBOL_KINDS,
|
|
298
|
+
limit: limit * 5
|
|
287
299
|
});
|
|
288
300
|
const matches = result.items
|
|
289
301
|
.filter((row) => {
|
|
@@ -292,14 +304,22 @@ export function findClass(svc, input) {
|
|
|
292
304
|
if (!isTypeSymbol)
|
|
293
305
|
return false;
|
|
294
306
|
const rowQualified = row.qualifiedName ?? row.filePath.replace(/\.java$/, "").replaceAll("/", ".");
|
|
295
|
-
return rowQualified === className ||
|
|
307
|
+
return (rowQualified === className ||
|
|
308
|
+
row.filePath === directPath ||
|
|
309
|
+
(resolvedFilePath != null && row.filePath === resolvedFilePath));
|
|
310
|
+
})
|
|
311
|
+
.map((row) => {
|
|
312
|
+
const rowQualified = row.qualifiedName ?? row.filePath.replace(/\.java$/, "").replaceAll("/", ".");
|
|
313
|
+
// For an inner-class match the stored qualifiedName is the outer type; the
|
|
314
|
+
// caller asked for the full nested FQCN, so report that.
|
|
315
|
+
const isInnerMatch = rowQualified !== className && row.filePath !== directPath;
|
|
316
|
+
return {
|
|
317
|
+
qualifiedName: isInnerMatch ? className : rowQualified,
|
|
318
|
+
filePath: row.filePath,
|
|
319
|
+
line: row.line,
|
|
320
|
+
symbolKind: row.symbolKind
|
|
321
|
+
};
|
|
296
322
|
})
|
|
297
|
-
.map((row) => ({
|
|
298
|
-
qualifiedName: row.qualifiedName ?? row.filePath.replace(/\.java$/, "").replaceAll("/", "."),
|
|
299
|
-
filePath: row.filePath,
|
|
300
|
-
line: row.line,
|
|
301
|
-
symbolKind: row.symbolKind
|
|
302
|
-
}))
|
|
303
323
|
.slice(0, limit);
|
|
304
324
|
const partialVanillaLookup = hasPartialNetMinecraftCoverage(artifact.qualityFlags) && looksLikeDeobfuscatedClassName(className);
|
|
305
325
|
const filteredMatches = partialVanillaLookup && matches.every((match) => !match.qualifiedName.startsWith("net.minecraft.") && !match.qualifiedName.startsWith("com.mojang."))
|
|
@@ -317,6 +337,7 @@ export function findClass(svc, input) {
|
|
|
317
337
|
artifactId,
|
|
318
338
|
query: className,
|
|
319
339
|
match: "exact",
|
|
340
|
+
symbolKinds: TYPE_SYMBOL_KINDS,
|
|
320
341
|
limit: limit * 5
|
|
321
342
|
});
|
|
322
343
|
const matches = [];
|
|
@@ -587,6 +608,27 @@ export async function getClassSource(svc, input) {
|
|
|
587
608
|
requestedMapping,
|
|
588
609
|
mappingApplied: activeMappingApplied
|
|
589
610
|
});
|
|
611
|
+
const nextStartLine = snippet.nextStartLine;
|
|
612
|
+
// Continuation guidance: when output was truncated and was not redirected to
|
|
613
|
+
// a file, hand the caller the next line to read plus a replayable call that
|
|
614
|
+
// re-reads from the already-resolved artifact (no re-resolution needed).
|
|
615
|
+
// nextStartLine is only set for snippet/full mode, so `mode` here is never
|
|
616
|
+
// "metadata". The caller's original endLine window is preserved so the
|
|
617
|
+
// continuation never reads past the requested range.
|
|
618
|
+
const continuation = nextStartLine != null && !resolvedOutputFile
|
|
619
|
+
? buildSuggestedCall({
|
|
620
|
+
tool: "get-class-source",
|
|
621
|
+
params: {
|
|
622
|
+
className,
|
|
623
|
+
target: { kind: "artifact", artifactId: activeArtifactId },
|
|
624
|
+
mode,
|
|
625
|
+
startLine: nextStartLine,
|
|
626
|
+
...(input.endLine != null ? { endLine: input.endLine } : {}),
|
|
627
|
+
...(input.maxLines != null ? { maxLines: input.maxLines } : {}),
|
|
628
|
+
...(input.maxChars != null ? { maxChars: input.maxChars } : {})
|
|
629
|
+
}
|
|
630
|
+
})
|
|
631
|
+
: undefined;
|
|
590
632
|
return {
|
|
591
633
|
className,
|
|
592
634
|
mode,
|
|
@@ -598,6 +640,8 @@ export async function getClassSource(svc, input) {
|
|
|
598
640
|
},
|
|
599
641
|
truncated,
|
|
600
642
|
...(charsTruncated ? { charsTruncated } : {}),
|
|
643
|
+
...(snippet.outOfRange ? { outOfRange: true } : {}),
|
|
644
|
+
...(nextStartLine != null ? { nextStartLine } : {}),
|
|
601
645
|
origin: activeOrigin,
|
|
602
646
|
artifactId: activeArtifactId,
|
|
603
647
|
requestedMapping,
|
|
@@ -611,6 +655,7 @@ export async function getClassSource(svc, input) {
|
|
|
611
655
|
isDecompiled: activeOrigin === "decompiled",
|
|
612
656
|
qualityFlags: activeQualityFlags
|
|
613
657
|
}),
|
|
658
|
+
...(continuation?.suggestedCall ? { suggestedCall: continuation.suggestedCall } : {}),
|
|
614
659
|
...(resolvedOutputFile ? { outputFile: resolvedOutputFile } : {}),
|
|
615
660
|
warnings
|
|
616
661
|
};
|
|
@@ -629,7 +674,7 @@ export async function getClassMembers(svc, input) {
|
|
|
629
674
|
const includeInherited = input.includeInherited ?? false;
|
|
630
675
|
const memberPattern = normalizeOptionalString(input.memberPattern);
|
|
631
676
|
const parsedMaxMembers = normalizeStrictPositiveInt(input.maxMembers, "maxMembers");
|
|
632
|
-
const maxMembers = parsedMaxMembers == null ?
|
|
677
|
+
const maxMembers = parsedMaxMembers == null ? 150 : Math.min(parsedMaxMembers, 5000);
|
|
633
678
|
const normalizedArtifactId = normalizeOptionalString(input.artifactId);
|
|
634
679
|
if (normalizedArtifactId && input.target) {
|
|
635
680
|
throw createError({
|
|
@@ -759,7 +804,23 @@ export async function getClassMembers(svc, input) {
|
|
|
759
804
|
}
|
|
760
805
|
catch (error) {
|
|
761
806
|
if (isAppError(error) && error.code === ERROR_CODES.CLASS_NOT_FOUND) {
|
|
762
|
-
|
|
807
|
+
// Re-raise with the shared recovery shape (find-class/api-matrix
|
|
808
|
+
// suggestedCall, namespace + scope hints) instead of the sparse bytecode
|
|
809
|
+
// error, so members and source agree on CLASS_NOT_FOUND guidance.
|
|
810
|
+
throw buildClassSourceNotFoundError(svc, {
|
|
811
|
+
artifactId,
|
|
812
|
+
className,
|
|
813
|
+
lookupClassName,
|
|
814
|
+
mappingApplied,
|
|
815
|
+
requestedMapping,
|
|
816
|
+
qualityFlags,
|
|
817
|
+
attemptedBinaryFallback: true,
|
|
818
|
+
targetKind: input.target?.kind,
|
|
819
|
+
targetValue: input.target && "value" in input.target ? input.target.value : undefined,
|
|
820
|
+
scope: input.scope,
|
|
821
|
+
projectPath: input.projectPath,
|
|
822
|
+
version
|
|
823
|
+
});
|
|
763
824
|
}
|
|
764
825
|
binaryExtractionFailed = true;
|
|
765
826
|
binaryExtractionFailureReason = error instanceof Error ? error.message : String(error);
|
|
@@ -787,11 +848,29 @@ export async function getClassMembers(svc, input) {
|
|
|
787
848
|
warnings
|
|
788
849
|
});
|
|
789
850
|
const counts = remapped.counts;
|
|
790
|
-
|
|
851
|
+
// Offset cursor over the flat [constructors, fields, methods] member sequence.
|
|
852
|
+
// The context key ties a cursor to this exact query so a stale cursor restarts.
|
|
853
|
+
const memberCursorContext = buildPageContextKey([
|
|
854
|
+
artifactId,
|
|
855
|
+
lookupClassName,
|
|
856
|
+
requestedMapping,
|
|
857
|
+
mappingApplied,
|
|
858
|
+
access,
|
|
859
|
+
includeSynthetic,
|
|
860
|
+
includeInherited,
|
|
861
|
+
memberPattern
|
|
862
|
+
]);
|
|
863
|
+
const { offset: memberOffset, cursorIgnored: memberCursorIgnored } = resolveCursorOffset(input.cursor, memberCursorContext);
|
|
864
|
+
const sliced = sliceMembersWithLimit(remapped, counts.total, maxMembers, warnings, memberOffset);
|
|
791
865
|
const constructors = sliced.constructors;
|
|
792
866
|
const fields = sliced.fields;
|
|
793
867
|
const methods = sliced.methods;
|
|
868
|
+
// Slim the wire member shape: hoist a shared ownerFqn, drop accessFlags, omit
|
|
869
|
+
// isSynthetic:false, and drop FIELD jvmDescriptor unless includeDescriptors.
|
|
870
|
+
// Internal SignatureMember arrays above stay intact.
|
|
871
|
+
const projectedMembers = projectMembersForWire({ constructors, fields, methods }, includeInherited, input.includeDescriptors ?? false);
|
|
794
872
|
const truncated = sliced.truncated;
|
|
873
|
+
const nextCursor = sliced.nextOffset != null ? encodeOffsetCursor(sliced.nextOffset, memberCursorContext) : undefined;
|
|
795
874
|
const normalizedProvenance = provenance ??
|
|
796
875
|
buildFallbackProvenance(svc, {
|
|
797
876
|
artifactId,
|
|
@@ -843,7 +922,7 @@ export async function getClassMembers(svc, input) {
|
|
|
843
922
|
suggestedCall = buildSuggestedCall({
|
|
844
923
|
tool: "get-class-source",
|
|
845
924
|
params: {
|
|
846
|
-
target: {
|
|
925
|
+
target: { kind: "artifact", artifactId },
|
|
847
926
|
className,
|
|
848
927
|
mode: "snippet",
|
|
849
928
|
mapping: requestedMapping
|
|
@@ -861,13 +940,11 @@ export async function getClassMembers(svc, input) {
|
|
|
861
940
|
}
|
|
862
941
|
return {
|
|
863
942
|
className,
|
|
864
|
-
members:
|
|
865
|
-
constructors,
|
|
866
|
-
fields,
|
|
867
|
-
methods
|
|
868
|
-
},
|
|
943
|
+
members: projectedMembers,
|
|
869
944
|
counts,
|
|
870
945
|
truncated,
|
|
946
|
+
...(nextCursor ? { nextCursor } : {}),
|
|
947
|
+
...(memberCursorIgnored ? { cursorIgnored: true } : {}),
|
|
871
948
|
context: signatureContext,
|
|
872
949
|
origin,
|
|
873
950
|
artifactId,
|
package/dist/source/indexer.js
CHANGED
|
@@ -43,8 +43,8 @@ export async function indexArtifact(svc, input) {
|
|
|
43
43
|
}
|
|
44
44
|
const artifact = svc.getArtifact(artifactId);
|
|
45
45
|
const force = input.force ?? false;
|
|
46
|
-
const hasFiles = svc.filesRepo.listFiles(artifact.artifactId, { limit: 1 }).items.length > 0;
|
|
47
46
|
const meta = svc.indexMetaRepo.get(artifact.artifactId);
|
|
47
|
+
const hasFiles = meta ? meta.filesCount > 0 : false;
|
|
48
48
|
const expectedSignature = artifact.artifactSignature ?? fallbackArtifactSignature(artifact.artifactId);
|
|
49
49
|
const reason = resolveIndexRebuildReason({
|
|
50
50
|
force,
|
|
@@ -291,8 +291,11 @@ export function getArtifact(svc, artifactId) {
|
|
|
291
291
|
}
|
|
292
292
|
export async function ingestIfNeeded(svc, resolved) {
|
|
293
293
|
const existing = svc.artifactsRepo.getArtifact(resolved.artifactId);
|
|
294
|
-
const hasFiles = svc.filesRepo.listFiles(resolved.artifactId, { limit: 1 }).items.length > 0;
|
|
295
294
|
const meta = svc.indexMetaRepo.get(resolved.artifactId);
|
|
295
|
+
// Derive hasFiles from meta instead of a separate listFiles probe: when meta is
|
|
296
|
+
// absent the reason is "missing_meta" regardless of hasFiles, and when present
|
|
297
|
+
// meta.filesCount is the authoritative count written alongside the file rows.
|
|
298
|
+
const hasFiles = meta ? meta.filesCount > 0 : false;
|
|
296
299
|
const reason = resolveIndexRebuildReason({
|
|
297
300
|
force: false,
|
|
298
301
|
expectedSignature: resolved.artifactSignature,
|
package/dist/source/search.d.ts
CHANGED
|
@@ -44,7 +44,7 @@ export declare function searchClassSource(svc: SourceService, input: SearchClass
|
|
|
44
44
|
export declare function searchSymbolIntent(svc: SourceService, artifactId: string, query: string, match: SearchMatch, scope: SearchScope | undefined, regexPattern: RegExp | undefined, onHit: (hit: SearchSourceHit) => void): void;
|
|
45
45
|
export declare function searchTextIntentIndexed(svc: SourceService, artifactId: string, query: string, match: SearchMatch, scope: SearchScope | undefined, onHit: (hit: SearchSourceHit) => void): void;
|
|
46
46
|
export declare function searchPathIntentIndexed(svc: SourceService, artifactId: string, query: string, match: SearchMatch, scope: SearchScope | undefined, onHit: (hit: SearchSourceHit) => void): void;
|
|
47
|
-
export declare function searchTextIntent(svc: SourceService, artifactId: string, query: string, match: SearchMatch, scope: SearchScope | undefined, regexPattern: RegExp | undefined, onHit: (hit: SearchSourceHit) => void): void;
|
|
47
|
+
export declare function searchTextIntent(svc: SourceService, artifactId: string, query: string, match: SearchMatch, scope: SearchScope | undefined, regexPattern: RegExp | undefined, onHit: (hit: SearchSourceHit) => void, onWarning?: (warning: string) => void): void;
|
|
48
48
|
export declare function searchPathIntent(svc: SourceService, artifactId: string, query: string, match: SearchMatch, scope: SearchScope | undefined, regexPattern: RegExp | undefined, onHit: (hit: SearchSourceHit) => void): void;
|
|
49
49
|
export declare function findSymbolHits(svc: SourceService, artifactId: string, query: string, match: SearchMatch, scope: SearchScope | undefined, regexPattern: RegExp | undefined): IndexedSymbolHit[];
|
|
50
50
|
export declare function indexedCandidateLimit(svc: SourceService): number;
|
package/dist/source/search.js
CHANGED
|
@@ -182,6 +182,11 @@ export function findContentMatchIndex(content, query, match, pattern) {
|
|
|
182
182
|
}
|
|
183
183
|
const normalizedContent = toLower(content);
|
|
184
184
|
const normalizedQuery = toLower(query);
|
|
185
|
+
// Both "contains" and "prefix" map to a substring search here. For free-text
|
|
186
|
+
// content, `prefix` is intentionally treated as "contains": file bodies are
|
|
187
|
+
// searched grep-style and the default match for intent=text resolves to
|
|
188
|
+
// "prefix" (see normalizeMatch), so anchoring at the start of the file would
|
|
189
|
+
// yield almost no hits. Symbol/path prefix anchoring lives in findMatchIndex.
|
|
185
190
|
return normalizedContent.indexOf(normalizedQuery);
|
|
186
191
|
}
|
|
187
192
|
export function scoreSymbolMatch(match, index, symbolKind) {
|
|
@@ -336,6 +341,10 @@ export async function searchClassSource(svc, input) {
|
|
|
336
341
|
});
|
|
337
342
|
const decodedCursor = decodeSearchCursor(input.cursor);
|
|
338
343
|
const cursor = decodedCursor?.contextKey === cursorContext ? decodedCursor : undefined;
|
|
344
|
+
// A provided cursor that did not decode, or whose context key does not match
|
|
345
|
+
// this query, is silently dropped and the scan restarts from page one. Flag
|
|
346
|
+
// it so callers do not assume they are continuing a previous page.
|
|
347
|
+
const cursorIgnored = input.cursor != null && cursor == null;
|
|
339
348
|
const accumulator = createSearchHitAccumulator(limit, cursor);
|
|
340
349
|
const indexedSearchEnabled = svc.config.indexedSearchEnabled !== false;
|
|
341
350
|
if (match === "regex") {
|
|
@@ -345,13 +354,16 @@ export async function searchClassSource(svc, input) {
|
|
|
345
354
|
const recordHit = (hit) => {
|
|
346
355
|
accumulator.add(hit);
|
|
347
356
|
};
|
|
357
|
+
const recordWarning = (warning) => {
|
|
358
|
+
searchWarnings.push(warning);
|
|
359
|
+
};
|
|
348
360
|
const tokenOnlyTextIntent = intent === "text" && queryMode === "token";
|
|
349
361
|
if (intent === "symbol") {
|
|
350
362
|
searchSymbolIntent(svc, artifact.artifactId, query, match, scope, regexPattern, recordHit);
|
|
351
363
|
}
|
|
352
364
|
else if (queryMode === "literal" && intent === "text") {
|
|
353
365
|
svc.metrics.recordSearchFallback();
|
|
354
|
-
searchTextIntent(svc, artifact.artifactId, query, match, scope, regexPattern, recordHit);
|
|
366
|
+
searchTextIntent(svc, artifact.artifactId, query, match, scope, regexPattern, recordHit, recordWarning);
|
|
355
367
|
}
|
|
356
368
|
else if (!indexedSearchEnabled) {
|
|
357
369
|
svc.metrics.recordIndexedDisabled();
|
|
@@ -361,7 +373,7 @@ export async function searchClassSource(svc, input) {
|
|
|
361
373
|
searchPathIntent(svc, artifact.artifactId, query, match, scope, regexPattern, recordHit);
|
|
362
374
|
}
|
|
363
375
|
else {
|
|
364
|
-
searchTextIntent(svc, artifact.artifactId, query, match, scope, regexPattern, recordHit);
|
|
376
|
+
searchTextIntent(svc, artifact.artifactId, query, match, scope, regexPattern, recordHit, recordWarning);
|
|
365
377
|
}
|
|
366
378
|
}
|
|
367
379
|
}
|
|
@@ -388,7 +400,7 @@ export async function searchClassSource(svc, input) {
|
|
|
388
400
|
searchPathIntent(svc, artifact.artifactId, query, match, scope, regexPattern, recordHit);
|
|
389
401
|
}
|
|
390
402
|
else {
|
|
391
|
-
searchTextIntent(svc, artifact.artifactId, query, match, scope, regexPattern, recordHit);
|
|
403
|
+
searchTextIntent(svc, artifact.artifactId, query, match, scope, regexPattern, recordHit, recordWarning);
|
|
392
404
|
}
|
|
393
405
|
}
|
|
394
406
|
}
|
|
@@ -400,7 +412,7 @@ export async function searchClassSource(svc, input) {
|
|
|
400
412
|
searchPathIntent(svc, artifact.artifactId, query, match, scope, regexPattern, recordHit);
|
|
401
413
|
}
|
|
402
414
|
else {
|
|
403
|
-
searchTextIntent(svc, artifact.artifactId, query, match, scope, regexPattern, recordHit);
|
|
415
|
+
searchTextIntent(svc, artifact.artifactId, query, match, scope, regexPattern, recordHit, recordWarning);
|
|
404
416
|
}
|
|
405
417
|
}
|
|
406
418
|
}
|
|
@@ -415,6 +427,7 @@ export async function searchClassSource(svc, input) {
|
|
|
415
427
|
return {
|
|
416
428
|
hits: page,
|
|
417
429
|
nextCursor,
|
|
430
|
+
...(cursorIgnored ? { cursorIgnored: true } : {}),
|
|
418
431
|
mappingApplied: artifact.mappingApplied ?? "obfuscated",
|
|
419
432
|
returnedNamespace: artifact.mappingApplied ?? "obfuscated",
|
|
420
433
|
artifactContents: svc.buildArtifactContentsSummary({
|
|
@@ -534,39 +547,98 @@ export function searchPathIntentIndexed(svc, artifactId, query, match, scope, on
|
|
|
534
547
|
});
|
|
535
548
|
}
|
|
536
549
|
}
|
|
537
|
-
|
|
538
|
-
|
|
550
|
+
const ASCII_RE = /^[\x00-\x7F]*$/;
|
|
551
|
+
/** True when every code unit is ASCII, so SQLite LIKE folds case identically to JS. */
|
|
552
|
+
function isAsciiNeedle(query) {
|
|
553
|
+
return ASCII_RE.test(query);
|
|
554
|
+
}
|
|
555
|
+
function searchScanBudgetWarning(byteBudget) {
|
|
556
|
+
return `search scan budget of ${byteBudget} bytes reached; results may be incomplete. Narrow the query or scope (packagePrefix / fileGlob) to scan fewer files.`;
|
|
557
|
+
}
|
|
558
|
+
export function searchTextIntent(svc, artifactId, query, match, scope, regexPattern, onHit, onWarning) {
|
|
539
559
|
const glob = scope?.fileGlob ? buildGlobRegex(normalizePathStyle(scope.fileGlob)) : undefined;
|
|
560
|
+
const byteBudget = Math.max(1, svc.config.searchScanMaxBytes ?? Number.MAX_SAFE_INTEGER);
|
|
561
|
+
let scannedBytes = 0;
|
|
562
|
+
let truncated = false;
|
|
563
|
+
const passesScope = (filePath) => checkPackagePrefix(filePath, scope?.packagePrefix) && (!glob || glob.test(filePath));
|
|
564
|
+
const emitIfMatch = (filePath, content) => {
|
|
565
|
+
const contentIndex = match === "regex"
|
|
566
|
+
? matchRegexIndex(content, regexPattern)
|
|
567
|
+
: findContentMatchIndex(content, query, match);
|
|
568
|
+
if (contentIndex < 0) {
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
onHit({
|
|
572
|
+
filePath,
|
|
573
|
+
score: scoreTextMatch(match, contentIndex),
|
|
574
|
+
matchedIn: "content",
|
|
575
|
+
reasonCodes: ["content_match", `text_${match}`]
|
|
576
|
+
});
|
|
577
|
+
};
|
|
578
|
+
// FAST PATH: an ASCII, non-regex needle narrows candidates via a content LIKE
|
|
579
|
+
// prefilter (a proven superset of the JS match), so only matching files are
|
|
580
|
+
// hydrated instead of scanning the whole corpus. The unchanged JS post-verify in
|
|
581
|
+
// emitIfMatch removes any over-included rows.
|
|
582
|
+
if (match !== "regex" && isAsciiNeedle(query)) {
|
|
583
|
+
const candidateLimit = indexedCandidateLimitForMatch(svc, match);
|
|
584
|
+
const { filePaths, scannedRows } = svc.filesRepo.searchContentLikeCandidatePaths(artifactId, query, candidateLimit);
|
|
585
|
+
svc.metrics.recordSearchDbRoundtrip();
|
|
586
|
+
svc.metrics.recordSearchRowsScanned(scannedRows);
|
|
587
|
+
// The prefilter caps candidates at candidateLimit ordered by file_path. Below the
|
|
588
|
+
// cap the candidate set is exhaustive, so the JS post-verify yields exactly the old
|
|
589
|
+
// full-scan hits. AT the cap more matches may exist beyond the path-ordered cut, and
|
|
590
|
+
// dropping them would change the scored top-K page and totalApprox/cursor — so fall
|
|
591
|
+
// through to the exhaustive paged scan below to preserve identical results.
|
|
592
|
+
if (scannedRows < candidateLimit) {
|
|
593
|
+
svc.metrics.recordSearchLikePrefilter();
|
|
594
|
+
const scopedPaths = filePaths.filter(passesScope);
|
|
595
|
+
const rows = svc.filesRepo.getFileContentsByPaths(artifactId, scopedPaths);
|
|
596
|
+
svc.metrics.recordSearchDbRoundtrip();
|
|
597
|
+
svc.metrics.recordSearchRowsScanned(rows.length);
|
|
598
|
+
for (const row of rows) {
|
|
599
|
+
if (scannedBytes >= byteBudget) {
|
|
600
|
+
truncated = true;
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
603
|
+
scannedBytes += row.contentBytes;
|
|
604
|
+
emitIfMatch(row.filePath, row.content);
|
|
605
|
+
}
|
|
606
|
+
if (truncated) {
|
|
607
|
+
svc.metrics.recordSearchScanTruncated();
|
|
608
|
+
onWarning?.(searchScanBudgetWarning(byteBudget));
|
|
609
|
+
}
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
// Cap overflow: fall through to the exhaustive scan (rare; only common-token queries).
|
|
613
|
+
}
|
|
614
|
+
// SLOW PATH: regex (cannot be pushed to SQL) or non-ASCII needles (SQLite LIKE
|
|
615
|
+
// would fold case differently than JS) scan the corpus page by page.
|
|
616
|
+
const pageSize = Math.max(1, svc.config.searchScanPageSize ?? 250);
|
|
540
617
|
let cursor = undefined;
|
|
541
|
-
while (true) {
|
|
618
|
+
outer: while (true) {
|
|
542
619
|
const page = svc.filesRepo.listFileRows(artifactId, { limit: pageSize, cursor });
|
|
543
620
|
svc.metrics.recordSearchDbRoundtrip();
|
|
544
621
|
svc.metrics.recordSearchRowsScanned(page.items.length);
|
|
545
622
|
for (const row of page.items) {
|
|
546
|
-
if (!
|
|
623
|
+
if (!passesScope(row.filePath)) {
|
|
547
624
|
continue;
|
|
548
625
|
}
|
|
549
|
-
if (
|
|
550
|
-
|
|
626
|
+
if (scannedBytes >= byteBudget) {
|
|
627
|
+
truncated = true;
|
|
628
|
+
break outer;
|
|
551
629
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
: findContentMatchIndex(row.content, query, match);
|
|
555
|
-
if (contentIndex < 0) {
|
|
556
|
-
continue;
|
|
557
|
-
}
|
|
558
|
-
onHit({
|
|
559
|
-
filePath: row.filePath,
|
|
560
|
-
score: scoreTextMatch(match, contentIndex),
|
|
561
|
-
matchedIn: "content",
|
|
562
|
-
reasonCodes: ["content_match", `text_${match}`]
|
|
563
|
-
});
|
|
630
|
+
scannedBytes += row.contentBytes;
|
|
631
|
+
emitIfMatch(row.filePath, row.content);
|
|
564
632
|
}
|
|
565
633
|
if (!page.nextCursor) {
|
|
566
634
|
break;
|
|
567
635
|
}
|
|
568
636
|
cursor = page.nextCursor;
|
|
569
637
|
}
|
|
638
|
+
if (truncated) {
|
|
639
|
+
svc.metrics.recordSearchScanTruncated();
|
|
640
|
+
onWarning?.(searchScanBudgetWarning(byteBudget));
|
|
641
|
+
}
|
|
570
642
|
}
|
|
571
643
|
export function searchPathIntent(svc, artifactId, query, match, scope, regexPattern, onHit) {
|
|
572
644
|
const pageSize = Math.max(1, svc.config.searchScanPageSize ?? 250);
|
|
@@ -4,7 +4,8 @@ import type { ValidateMixinInput, ValidateMixinOptions, ValidateMixinOutput } fr
|
|
|
4
4
|
import { type StageEmitter } from "../stage-emitter.js";
|
|
5
5
|
import type { MappingSourcePriority } from "../types.js";
|
|
6
6
|
import type { FindMappingOutput as MappingFindMappingOutput } from "../mapping-service.js";
|
|
7
|
-
export type ValidateMixinSingleInput = Omit<ValidateMixinInput, "input"> & {
|
|
7
|
+
export type ValidateMixinSingleInput = Omit<ValidateMixinInput, "input" | "version"> & {
|
|
8
|
+
version: string;
|
|
8
9
|
source?: string;
|
|
9
10
|
sourcePath?: string;
|
|
10
11
|
batchCaches?: {
|
|
@@ -159,7 +159,43 @@ export async function validateMixin(svc, input, options = {}) {
|
|
|
159
159
|
throw annotateValidateMixinError(err, "input-validation");
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
|
-
|
|
162
|
+
/**
|
|
163
|
+
* Resolve the Minecraft version for a validate-mixin request. Uses the explicit
|
|
164
|
+
* `version` when present; otherwise detects it from gradle.properties for
|
|
165
|
+
* project mode or when preferProjectVersion is set with a projectPath. Throws an
|
|
166
|
+
* actionable INVALID_INPUT error when no version can be determined.
|
|
167
|
+
*/
|
|
168
|
+
async function resolveRequestedMinecraftVersion(svc, input) {
|
|
169
|
+
const explicit = input.version?.trim();
|
|
170
|
+
if (explicit) {
|
|
171
|
+
return explicit;
|
|
172
|
+
}
|
|
173
|
+
const projectPathCandidate = input.projectPath ?? (input.input.mode === "project" ? input.input.path : undefined);
|
|
174
|
+
if (projectPathCandidate) {
|
|
175
|
+
const normalizedProjectPath = resolveMixinInputPath(projectPathCandidate, "path");
|
|
176
|
+
const detected = await svc.workspaceMappingService.detectProjectMinecraftVersion(normalizedProjectPath);
|
|
177
|
+
if (detected?.trim()) {
|
|
178
|
+
return detected.trim();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
throw createError({
|
|
182
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
183
|
+
message: "Could not detect a Minecraft version from the project. Pass version explicitly, or ensure gradle.properties declares minecraft_version.",
|
|
184
|
+
details: {
|
|
185
|
+
failedStage: "input-validation",
|
|
186
|
+
nextAction: 'Pass version (e.g. "1.21.10"), or set preferProjectVersion=true with a projectPath whose gradle.properties contains minecraft_version.'
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
async function runValidateMixinDispatcher(svc, rawInput, options = {}) {
|
|
191
|
+
const requestedVersion = await resolveRequestedMinecraftVersion(svc, rawInput);
|
|
192
|
+
// Service-level default so direct/internal/entry-tool callers (not just the MCP
|
|
193
|
+
// wire boundary, where Zod applies it) get the lean summary-first report shape.
|
|
194
|
+
const input = {
|
|
195
|
+
...rawInput,
|
|
196
|
+
version: requestedVersion,
|
|
197
|
+
reportMode: rawInput.reportMode ?? "summary-first"
|
|
198
|
+
};
|
|
163
199
|
const { input: sourceInput, ...sharedInput } = input;
|
|
164
200
|
const mode = sourceInput.mode;
|
|
165
201
|
const stageEmitter = options.stageEmitter ?? NOOP_STAGE_EMITTER;
|
|
@@ -495,7 +531,9 @@ async function finalizeValidateMixinPipeline(svc, ctx) {
|
|
|
495
531
|
result.aggregatedWarnings = undefined;
|
|
496
532
|
result.toolHealth = undefined;
|
|
497
533
|
result.confidenceBreakdown = undefined;
|
|
498
|
-
|
|
534
|
+
// resolutionTrace only exists when explain=true; keep it then so explain still
|
|
535
|
+
// delivers the per-issue trace even under the compact report shape.
|
|
536
|
+
if (result.provenance && input.explain !== true) {
|
|
499
537
|
result.provenance.resolutionTrace = undefined;
|
|
500
538
|
}
|
|
501
539
|
}
|
|
@@ -676,6 +714,9 @@ function applyValidateMixinOutputCompaction(output, input) {
|
|
|
676
714
|
? true
|
|
677
715
|
: warningCandidates.every((entry) => sameStringArray(entry, warningCandidates[0]));
|
|
678
716
|
if (input.reportMode === "summary-first") {
|
|
717
|
+
// explain=true keeps the per-result heavy detail (resolvedMembers/toolHealth)
|
|
718
|
+
// even under summary-first, so the diagnostic opt-in still works.
|
|
719
|
+
const keepHeavyDetail = input.explain === true;
|
|
679
720
|
nextOutput = {
|
|
680
721
|
...nextOutput,
|
|
681
722
|
results: nextOutput.results.map((entry) => (entry.result
|
|
@@ -686,8 +727,8 @@ function applyValidateMixinOutputCompaction(output, input) {
|
|
|
686
727
|
warnings: canHoistWarnings ? [] : entry.result.warnings,
|
|
687
728
|
structuredWarnings: undefined,
|
|
688
729
|
aggregatedWarnings: undefined,
|
|
689
|
-
resolvedMembers: undefined,
|
|
690
|
-
toolHealth: undefined,
|
|
730
|
+
resolvedMembers: keepHeavyDetail ? entry.result.resolvedMembers : undefined,
|
|
731
|
+
toolHealth: keepHeavyDetail ? entry.result.toolHealth : undefined,
|
|
691
732
|
confidenceBreakdown: undefined,
|
|
692
733
|
provenance: canHoistProvenance ? undefined : entry.result.provenance
|
|
693
734
|
}
|
|
@@ -256,15 +256,21 @@ export async function synthesizeDependencyTarget(svc, input, dep) {
|
|
|
256
256
|
nextAction,
|
|
257
257
|
...buildSuggestedCall({
|
|
258
258
|
tool: "resolve-artifact",
|
|
259
|
-
params:
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
259
|
+
params: undefined,
|
|
260
|
+
examples: [
|
|
261
|
+
{
|
|
262
|
+
params: {
|
|
263
|
+
target: {
|
|
264
|
+
kind: "dependency",
|
|
265
|
+
group,
|
|
266
|
+
name,
|
|
267
|
+
version: "<your-version>"
|
|
268
|
+
},
|
|
269
|
+
projectPath
|
|
270
|
+
},
|
|
271
|
+
reason: "Replace <your-version> with the dependency version, or declare it in gradle.properties."
|
|
272
|
+
}
|
|
273
|
+
]
|
|
268
274
|
})
|
|
269
275
|
}
|
|
270
276
|
});
|
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
import type { Readable } from "node:stream";
|
|
2
|
+
export declare function __getZipOpenCount(): number;
|
|
3
|
+
export declare function __resetZipOpenCount(): void;
|
|
4
|
+
interface ZipEntry {
|
|
5
|
+
fileName: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ZipFile {
|
|
8
|
+
readEntry(): void;
|
|
9
|
+
close(): void;
|
|
10
|
+
once(event: "entry", listener: (entry: ZipEntry) => void): this;
|
|
11
|
+
once(event: "end", listener: () => void): this;
|
|
12
|
+
once(event: "error", listener: (error: Error) => void): this;
|
|
13
|
+
removeListener(event: "entry", listener: (entry: ZipEntry) => void): this;
|
|
14
|
+
removeListener(event: "end", listener: () => void): this;
|
|
15
|
+
removeListener(event: "error", listener: (error: Error) => void): this;
|
|
16
|
+
openReadStream(entry: ZipEntry, callback: (error: Error | null, stream: Readable | null) => void): void;
|
|
17
|
+
}
|
|
1
18
|
export interface JavaEntryText {
|
|
2
19
|
filePath: string;
|
|
3
20
|
content: string;
|
|
@@ -12,6 +29,7 @@ export interface CollectMatchedJarEntriesOptions {
|
|
|
12
29
|
continueOnError?: boolean;
|
|
13
30
|
}
|
|
14
31
|
export declare function hasJavaSourceExtension(entryPath: string): boolean;
|
|
32
|
+
export declare function openZipFile(jarPath: string): Promise<ZipFile>;
|
|
15
33
|
export declare class EntryTooLargeError extends Error {
|
|
16
34
|
constructor(entryPath: string, jarPath: string, maxBytes: number);
|
|
17
35
|
}
|
|
@@ -20,10 +38,45 @@ export declare function listJavaEntries(jarPath: string): Promise<string[]>;
|
|
|
20
38
|
export declare function hasAnyJarEntry(jarPath: string, predicate: (entryPath: string) => boolean): Promise<boolean>;
|
|
21
39
|
export declare function readJarEntryAsUtf8(jarPath: string, entryPath: string): Promise<string>;
|
|
22
40
|
export declare function readJarEntryAsBuffer(jarPath: string, entryPath: string): Promise<Buffer>;
|
|
41
|
+
export interface JarEntryReader {
|
|
42
|
+
getEntryBuffer(entryPath: string): Promise<Buffer>;
|
|
43
|
+
close(): void;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Opens a jar ONCE, drains its central directory into a name->entry index, and
|
|
47
|
+
* serves repeated entry reads via O(1) lookup + openReadStream. Use this when a
|
|
48
|
+
* single logical operation reads many entries from the same jar (e.g. walking a
|
|
49
|
+
* class hierarchy) instead of calling {@link readJarEntryAsBuffer} per entry,
|
|
50
|
+
* which re-opens and re-scans the jar each time. Callers MUST close() the reader
|
|
51
|
+
* (in a finally) to release the file descriptor. getEntryBuffer throws the same
|
|
52
|
+
* INVALID_INPUT (unsafe path) / SOURCE_NOT_FOUND (missing entry) errors as
|
|
53
|
+
* readJarEntryAsBuffer; duplicate entry names resolve to the first occurrence.
|
|
54
|
+
*/
|
|
55
|
+
export declare function createJarEntryReader(jarPath: string): Promise<JarEntryReader>;
|
|
23
56
|
export declare function collectMatchedJarEntriesAsUtf8(jarPath: string, predicate: (entryPath: string) => boolean, options?: CollectMatchedJarEntriesOptions): Promise<JarEntryText[]>;
|
|
57
|
+
export interface JarEntryBuffer {
|
|
58
|
+
filePath: string;
|
|
59
|
+
content: Buffer;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Seam for injecting the jar-open primitive so callers/tests can count opens.
|
|
63
|
+
* Defaults to the module-level {@link openZipFile}.
|
|
64
|
+
*/
|
|
65
|
+
export interface JarReaderOpenDeps {
|
|
66
|
+
openZipFile?: (jarPath: string) => Promise<ZipFile>;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Raw-buffer counterpart of {@link collectMatchedJarEntriesAsUtf8}: opens the jar
|
|
70
|
+
* ONCE and returns the matched entries as raw Buffers (no UTF-8 decode), so binary
|
|
71
|
+
* inputs like `.class` bytes survive intact. Used to avoid re-opening the jar per
|
|
72
|
+
* sampled entry. Mirrors the sibling's maxBytes/maxEntries/continueOnError semantics
|
|
73
|
+
* and its finally-close, but takes an injectable open seam for open-count assertions.
|
|
74
|
+
*/
|
|
75
|
+
export declare function collectMatchedJarEntriesAsBuffers(jarPath: string, predicate: (entryPath: string) => boolean, options?: CollectMatchedJarEntriesOptions, deps?: JarReaderOpenDeps): Promise<JarEntryBuffer[]>;
|
|
24
76
|
export declare function iterateJavaEntriesAsUtf8(jarPath: string, maxBytes?: number): AsyncGenerator<JavaEntryText>;
|
|
25
77
|
export declare function readAllJavaEntriesAsUtf8(jarPath: string, maxBytes?: number): Promise<JavaEntryText[]>;
|
|
26
|
-
export declare function detectFabricLikeInputNamespace(inputJar: string): Promise<{
|
|
78
|
+
export declare function detectFabricLikeInputNamespace(inputJar: string, deps?: JarReaderOpenDeps): Promise<{
|
|
27
79
|
fromNamespace: "intermediary" | "mojang";
|
|
28
80
|
warnings: string[];
|
|
29
81
|
}>;
|
|
82
|
+
export {};
|