@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
package/dist/source-service.js
CHANGED
|
@@ -159,6 +159,7 @@ function clampLimit(limit, fallback, max) {
|
|
|
159
159
|
}
|
|
160
160
|
const MAX_REGEX_QUERY_LENGTH = 200;
|
|
161
161
|
const MAX_REGEX_RESULT_LIMIT = 100;
|
|
162
|
+
const TRACE_LIFECYCLE_MAX_CONCURRENCY = 3;
|
|
162
163
|
function normalizePathStyle(path) {
|
|
163
164
|
return path.replaceAll("\\", "/");
|
|
164
165
|
}
|
|
@@ -214,6 +215,18 @@ function normalizeOptionalProjectPath(projectPath) {
|
|
|
214
215
|
const normalized = normalizePathForHost(trimmed, undefined, "projectPath");
|
|
215
216
|
return isAbsolute(normalized) ? normalized : resolvePath(process.cwd(), normalized);
|
|
216
217
|
}
|
|
218
|
+
function looksLikeClassSegment(name) {
|
|
219
|
+
const trimmed = name.trim();
|
|
220
|
+
return /^[A-Z_$]/.test(trimmed);
|
|
221
|
+
}
|
|
222
|
+
function looksLikeJvmMethodDescriptor(descriptor) {
|
|
223
|
+
const trimmed = descriptor?.trim();
|
|
224
|
+
if (!trimmed || !trimmed.startsWith("(")) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
const closing = trimmed.indexOf(")");
|
|
228
|
+
return closing > 0 && closing < trimmed.length - 1;
|
|
229
|
+
}
|
|
217
230
|
function resolveGradleUserHomePath() {
|
|
218
231
|
const configured = process.env.GRADLE_USER_HOME?.trim();
|
|
219
232
|
if (!configured) {
|
|
@@ -278,16 +291,19 @@ function scopeToJarType(scope) {
|
|
|
278
291
|
}
|
|
279
292
|
function parseQualifiedMethodSymbol(symbol) {
|
|
280
293
|
const trimmed = symbol.trim();
|
|
281
|
-
const
|
|
282
|
-
|
|
294
|
+
const descriptorStart = trimmed.indexOf("(");
|
|
295
|
+
const qualifiedSymbol = descriptorStart >= 0 ? trimmed.slice(0, descriptorStart) : trimmed;
|
|
296
|
+
const inlineDescriptor = descriptorStart >= 0 ? trimmed.slice(descriptorStart).trim() : undefined;
|
|
297
|
+
const separator = qualifiedSymbol.lastIndexOf(".");
|
|
298
|
+
if (separator <= 0 || separator >= qualifiedSymbol.length - 1) {
|
|
283
299
|
throw createError({
|
|
284
300
|
code: ERROR_CODES.INVALID_INPUT,
|
|
285
301
|
message: `symbol must be in the form "fully.qualified.Class.method".`,
|
|
286
302
|
details: { symbol }
|
|
287
303
|
});
|
|
288
304
|
}
|
|
289
|
-
const className =
|
|
290
|
-
const methodName =
|
|
305
|
+
const className = qualifiedSymbol.slice(0, separator);
|
|
306
|
+
const methodName = qualifiedSymbol.slice(separator + 1);
|
|
291
307
|
if (!className ||
|
|
292
308
|
!methodName ||
|
|
293
309
|
className.includes("/") ||
|
|
@@ -299,7 +315,11 @@ function parseQualifiedMethodSymbol(symbol) {
|
|
|
299
315
|
details: { symbol }
|
|
300
316
|
});
|
|
301
317
|
}
|
|
302
|
-
return {
|
|
318
|
+
return {
|
|
319
|
+
className,
|
|
320
|
+
methodName,
|
|
321
|
+
...(inlineDescriptor ? { inlineDescriptor } : {})
|
|
322
|
+
};
|
|
303
323
|
}
|
|
304
324
|
function normalizeOptionalString(value) {
|
|
305
325
|
if (value == null) {
|
|
@@ -308,6 +328,27 @@ function normalizeOptionalString(value) {
|
|
|
308
328
|
const trimmed = value.trim();
|
|
309
329
|
return trimmed ? trimmed : undefined;
|
|
310
330
|
}
|
|
331
|
+
async function mapWithConcurrencyLimit(items, limit, mapper) {
|
|
332
|
+
if (items.length === 0) {
|
|
333
|
+
return [];
|
|
334
|
+
}
|
|
335
|
+
const results = new Array(items.length);
|
|
336
|
+
let nextIndex = 0;
|
|
337
|
+
const workerCount = Math.max(1, Math.min(Math.trunc(limit), items.length));
|
|
338
|
+
await Promise.all(Array.from({ length: workerCount }, async () => {
|
|
339
|
+
while (true) {
|
|
340
|
+
// Safe in Node's single-threaded event loop because no await occurs between
|
|
341
|
+
// reading and incrementing nextIndex inside this synchronous dispatch section.
|
|
342
|
+
const currentIndex = nextIndex;
|
|
343
|
+
nextIndex += 1;
|
|
344
|
+
if (currentIndex >= items.length) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
results[currentIndex] = await mapper(items[currentIndex], currentIndex);
|
|
348
|
+
}
|
|
349
|
+
}));
|
|
350
|
+
return results;
|
|
351
|
+
}
|
|
311
352
|
function normalizeStrictPositiveInt(value, field) {
|
|
312
353
|
if (value == null) {
|
|
313
354
|
return undefined;
|
|
@@ -1208,6 +1249,7 @@ export class SourceService {
|
|
|
1208
1249
|
const limit = clampLimit(input.limit, 20, searchLimitCap);
|
|
1209
1250
|
const regexPattern = match === "regex" ? compileRegex(query) : undefined;
|
|
1210
1251
|
const queryMode = input.queryMode ?? "auto";
|
|
1252
|
+
this.metrics.recordSearchQueryMode(queryMode);
|
|
1211
1253
|
const cursorContext = buildSearchCursorContext({
|
|
1212
1254
|
artifactId: artifact.artifactId,
|
|
1213
1255
|
query,
|
|
@@ -1227,7 +1269,6 @@ export class SourceService {
|
|
|
1227
1269
|
const recordHit = (hit) => {
|
|
1228
1270
|
accumulator.add(hit);
|
|
1229
1271
|
};
|
|
1230
|
-
const hasSeparators = /[._$]/.test(query);
|
|
1231
1272
|
const tokenOnlyTextIntent = intent === "text" && queryMode === "token";
|
|
1232
1273
|
if (intent === "symbol") {
|
|
1233
1274
|
this.searchSymbolIntent(artifact.artifactId, query, match, scope, regexPattern, recordHit);
|
|
@@ -1256,10 +1297,6 @@ export class SourceService {
|
|
|
1256
1297
|
}
|
|
1257
1298
|
else {
|
|
1258
1299
|
this.searchTextIntentIndexed(artifact.artifactId, query, match, scope, recordHit);
|
|
1259
|
-
// F-03: queryMode=auto fallback — when indexed returns 0 hits and query has separators, retry with literal scan
|
|
1260
|
-
if (queryMode === "auto" && hasSeparators && accumulator.currentCount() === 0) {
|
|
1261
|
-
this.searchTextIntent(artifact.artifactId, query, match, scope, regexPattern, recordHit);
|
|
1262
|
-
}
|
|
1263
1300
|
}
|
|
1264
1301
|
this.metrics.recordSearchIndexedHit();
|
|
1265
1302
|
}
|
|
@@ -1648,8 +1685,9 @@ export class SourceService {
|
|
|
1648
1685
|
}
|
|
1649
1686
|
async traceSymbolLifecycle(input) {
|
|
1650
1687
|
const mapping = normalizeMapping(input.mapping);
|
|
1651
|
-
const { className: userClassName, methodName: userMethodName } = parseQualifiedMethodSymbol(input.symbol);
|
|
1652
|
-
const descriptor = normalizeOptionalString(input.descriptor)
|
|
1688
|
+
const { className: userClassName, methodName: userMethodName, inlineDescriptor } = parseQualifiedMethodSymbol(input.symbol);
|
|
1689
|
+
const descriptor = normalizeOptionalString(input.descriptor)
|
|
1690
|
+
?? (looksLikeJvmMethodDescriptor(inlineDescriptor) ? normalizeOptionalString(inlineDescriptor) : undefined);
|
|
1653
1691
|
const includeTimeline = input.includeTimeline ?? false;
|
|
1654
1692
|
const includeSnapshots = input.includeSnapshots ?? false;
|
|
1655
1693
|
const maxVersions = clampLimit(input.maxVersions, 120, 400);
|
|
@@ -1705,60 +1743,75 @@ export class SourceService {
|
|
|
1705
1743
|
selectedVersions = selectedVersions.slice(selectedVersions.length - maxVersions);
|
|
1706
1744
|
warnings.push(`Version scan truncated to ${maxVersions} entries. Effective fromVersion is now "${selectedVersions[0]}".`);
|
|
1707
1745
|
}
|
|
1708
|
-
const
|
|
1709
|
-
|
|
1710
|
-
|
|
1746
|
+
const referenceVersion = selectedVersions[selectedVersions.length - 1];
|
|
1747
|
+
await this.rejectLifecycleClassLikeInput({
|
|
1748
|
+
symbol: input.symbol,
|
|
1749
|
+
className: userClassName,
|
|
1750
|
+
methodName: userMethodName,
|
|
1751
|
+
mapping,
|
|
1752
|
+
version: referenceVersion,
|
|
1753
|
+
sourcePriority: input.sourcePriority
|
|
1754
|
+
});
|
|
1755
|
+
const scannedResults = await mapWithConcurrencyLimit(selectedVersions, TRACE_LIFECYCLE_MAX_CONCURRENCY, async (version) => {
|
|
1756
|
+
const versionWarnings = [];
|
|
1711
1757
|
try {
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
]);
|
|
1718
|
-
resolvedSymbols = {
|
|
1719
|
-
className: obfuscatedClassName,
|
|
1720
|
-
methodName: obfuscatedMethod.name,
|
|
1721
|
-
methodDescriptor: obfuscatedMethod.descriptor
|
|
1722
|
-
};
|
|
1723
|
-
resolvedSymbolsByVersion.set(version, resolvedSymbols);
|
|
1724
|
-
}
|
|
1725
|
-
const resolvedJar = await this.versionService.resolveVersionJar(version);
|
|
1758
|
+
const [obfuscatedClassName, obfuscatedMethod, resolvedJar] = await Promise.all([
|
|
1759
|
+
this.resolveToObfuscatedClassName(userClassName, version, mapping, input.sourcePriority, versionWarnings),
|
|
1760
|
+
this.resolveToObfuscatedMemberName(userMethodName, userClassName, descriptor, "method", version, mapping, input.sourcePriority, versionWarnings),
|
|
1761
|
+
this.versionService.resolveVersionJar(version)
|
|
1762
|
+
]);
|
|
1726
1763
|
const signature = await this.explorerService.getSignature({
|
|
1727
|
-
fqn:
|
|
1764
|
+
fqn: obfuscatedClassName,
|
|
1728
1765
|
jarPath: resolvedJar.jarPath,
|
|
1729
1766
|
access: "all",
|
|
1730
1767
|
includeSynthetic: true
|
|
1731
1768
|
});
|
|
1732
|
-
const sameNameMethods = signature.methods.filter((method) => method.name ===
|
|
1733
|
-
const
|
|
1734
|
-
|
|
1769
|
+
const sameNameMethods = signature.methods.filter((method) => method.name === obfuscatedMethod.name);
|
|
1770
|
+
const effectiveDescriptor = obfuscatedMethod.descriptor ?? descriptor;
|
|
1771
|
+
const matchesDescriptor = effectiveDescriptor
|
|
1772
|
+
? sameNameMethods.some((method) => method.jvmDescriptor === effectiveDescriptor)
|
|
1735
1773
|
: sameNameMethods.length > 0;
|
|
1736
1774
|
const reason = !matchesDescriptor && descriptor && sameNameMethods.length > 0 ? "descriptor-mismatch" : undefined;
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1775
|
+
return {
|
|
1776
|
+
entry: {
|
|
1777
|
+
version,
|
|
1778
|
+
exists: matchesDescriptor,
|
|
1779
|
+
reason,
|
|
1780
|
+
determinate: true
|
|
1781
|
+
},
|
|
1782
|
+
warnings: versionWarnings
|
|
1783
|
+
};
|
|
1743
1784
|
}
|
|
1744
1785
|
catch (caughtError) {
|
|
1745
1786
|
if (isAppError(caughtError) && caughtError.code === ERROR_CODES.CLASS_NOT_FOUND) {
|
|
1746
|
-
|
|
1787
|
+
return {
|
|
1788
|
+
entry: {
|
|
1789
|
+
version,
|
|
1790
|
+
exists: false,
|
|
1791
|
+
reason: "class-not-found",
|
|
1792
|
+
determinate: true
|
|
1793
|
+
},
|
|
1794
|
+
warnings: versionWarnings
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1797
|
+
versionWarnings.push(`Failed to evaluate ${version}: ${caughtError instanceof Error ? caughtError.message : String(caughtError)}`);
|
|
1798
|
+
return {
|
|
1799
|
+
entry: {
|
|
1747
1800
|
version,
|
|
1748
1801
|
exists: false,
|
|
1749
|
-
reason: "
|
|
1750
|
-
determinate:
|
|
1751
|
-
}
|
|
1752
|
-
|
|
1753
|
-
}
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
reason: "unresolved",
|
|
1758
|
-
determinate: false
|
|
1759
|
-
});
|
|
1760
|
-
warnings.push(`Failed to evaluate ${version}: ${caughtError instanceof Error ? caughtError.message : String(caughtError)}`);
|
|
1802
|
+
reason: "unresolved",
|
|
1803
|
+
determinate: false
|
|
1804
|
+
},
|
|
1805
|
+
warnings: versionWarnings
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
finally {
|
|
1809
|
+
this.releaseLifecycleMappingGraph(version, input.sourcePriority);
|
|
1761
1810
|
}
|
|
1811
|
+
});
|
|
1812
|
+
const scanned = scannedResults.map((result) => result.entry);
|
|
1813
|
+
for (const result of scannedResults) {
|
|
1814
|
+
warnings.push(...result.warnings);
|
|
1762
1815
|
}
|
|
1763
1816
|
const determinate = scanned.filter((entry) => entry.determinate);
|
|
1764
1817
|
const present = determinate.filter((entry) => entry.exists);
|
|
@@ -2699,17 +2752,18 @@ export class SourceService {
|
|
|
2699
2752
|
path: this.resolveMixinInputPath(path, "path")
|
|
2700
2753
|
},
|
|
2701
2754
|
sourcePath: path
|
|
2702
|
-
})), input);
|
|
2755
|
+
})), input, []);
|
|
2703
2756
|
}
|
|
2704
2757
|
const resolvedInput = mode === "project"
|
|
2705
2758
|
? this.createProjectValidateMixinConfigInput(input)
|
|
2706
2759
|
: input;
|
|
2707
|
-
const configSources = await this.resolveMixinConfigSources(resolvedInput);
|
|
2760
|
+
const { sources: configSources, warnings: configWarnings } = await this.resolveMixinConfigSources(resolvedInput);
|
|
2708
2761
|
if (configSources.length === 0) {
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2762
|
+
const emptyOutput = this.buildValidateMixinOutput(mode, []);
|
|
2763
|
+
return this.applyValidateMixinOutputCompaction({
|
|
2764
|
+
...emptyOutput,
|
|
2765
|
+
warnings: [...new Set([...emptyOutput.warnings, ...configWarnings])]
|
|
2766
|
+
}, input);
|
|
2713
2767
|
}
|
|
2714
2768
|
return this.validateMixinMany(mode, configSources.map((entry) => ({
|
|
2715
2769
|
source: {
|
|
@@ -2719,7 +2773,7 @@ export class SourceService {
|
|
|
2719
2773
|
configPath: entry.configPath
|
|
2720
2774
|
},
|
|
2721
2775
|
sourcePath: entry.sourcePath
|
|
2722
|
-
})), resolvedInput);
|
|
2776
|
+
})), resolvedInput, configWarnings);
|
|
2723
2777
|
}
|
|
2724
2778
|
createProjectValidateMixinConfigInput(input) {
|
|
2725
2779
|
if (input.input.mode !== "project") {
|
|
@@ -3249,9 +3303,13 @@ export class SourceService {
|
|
|
3249
3303
|
}
|
|
3250
3304
|
async resolveMixinConfigSources(input) {
|
|
3251
3305
|
if (input.input.mode !== "config") {
|
|
3252
|
-
return
|
|
3306
|
+
return {
|
|
3307
|
+
sources: [],
|
|
3308
|
+
warnings: []
|
|
3309
|
+
};
|
|
3253
3310
|
}
|
|
3254
3311
|
const results = [];
|
|
3312
|
+
const warnings = [];
|
|
3255
3313
|
for (const rawConfigPath of input.input.configPaths) {
|
|
3256
3314
|
const resolvedConfigPath = this.resolveMixinInputPath(rawConfigPath, "configPath");
|
|
3257
3315
|
let configJson;
|
|
@@ -3272,6 +3330,7 @@ export class SourceService {
|
|
|
3272
3330
|
...(configJson.server ?? [])
|
|
3273
3331
|
];
|
|
3274
3332
|
if (classNames.length === 0) {
|
|
3333
|
+
warnings.push(`Mixin config "${resolvedConfigPath}" contains no mixin class entries.`);
|
|
3275
3334
|
continue;
|
|
3276
3335
|
}
|
|
3277
3336
|
const projectBase = input.projectPath
|
|
@@ -3306,9 +3365,12 @@ export class SourceService {
|
|
|
3306
3365
|
});
|
|
3307
3366
|
}
|
|
3308
3367
|
}
|
|
3309
|
-
return
|
|
3368
|
+
return {
|
|
3369
|
+
sources: results,
|
|
3370
|
+
warnings
|
|
3371
|
+
};
|
|
3310
3372
|
}
|
|
3311
|
-
async validateMixinMany(mode, entries, input) {
|
|
3373
|
+
async validateMixinMany(mode, entries, input, additionalWarnings) {
|
|
3312
3374
|
const results = [];
|
|
3313
3375
|
const batchWarningMode = input.warningMode ?? "aggregated";
|
|
3314
3376
|
const { input: _discardedInput, ...sharedInput } = input;
|
|
@@ -3335,7 +3397,13 @@ export class SourceService {
|
|
|
3335
3397
|
});
|
|
3336
3398
|
}
|
|
3337
3399
|
}
|
|
3338
|
-
|
|
3400
|
+
const output = this.buildValidateMixinOutput(mode, results);
|
|
3401
|
+
return this.applyValidateMixinOutputCompaction({
|
|
3402
|
+
...output,
|
|
3403
|
+
warnings: additionalWarnings.length === 0
|
|
3404
|
+
? output.warnings
|
|
3405
|
+
: [...new Set([...output.warnings, ...additionalWarnings])]
|
|
3406
|
+
}, input);
|
|
3339
3407
|
}
|
|
3340
3408
|
applyValidateMixinOutputCompaction(output, input) {
|
|
3341
3409
|
let nextOutput = output;
|
|
@@ -3617,6 +3685,7 @@ export class SourceService {
|
|
|
3617
3685
|
const candidateLimit = this.indexedCandidateLimitForMatch(match);
|
|
3618
3686
|
const indexed = this.filesRepo.searchFileCandidates(artifactId, {
|
|
3619
3687
|
query,
|
|
3688
|
+
match,
|
|
3620
3689
|
limit: candidateLimit,
|
|
3621
3690
|
mode: "text"
|
|
3622
3691
|
});
|
|
@@ -3701,13 +3770,20 @@ export class SourceService {
|
|
|
3701
3770
|
}
|
|
3702
3771
|
}
|
|
3703
3772
|
searchTextIntent(artifactId, query, match, scope, regexPattern, onHit) {
|
|
3704
|
-
const filePaths = this.loadScopedFilePaths(artifactId, scope);
|
|
3705
3773
|
const pageSize = Math.max(1, this.config.searchScanPageSize ?? 250);
|
|
3706
|
-
|
|
3707
|
-
|
|
3774
|
+
const glob = scope?.fileGlob ? buildGlobRegex(normalizePathStyle(scope.fileGlob)) : undefined;
|
|
3775
|
+
let cursor = undefined;
|
|
3776
|
+
while (true) {
|
|
3777
|
+
const page = this.filesRepo.listFileRows(artifactId, { limit: pageSize, cursor });
|
|
3708
3778
|
this.metrics.recordSearchDbRoundtrip();
|
|
3709
|
-
this.metrics.recordSearchRowsScanned(
|
|
3710
|
-
for (const row of
|
|
3779
|
+
this.metrics.recordSearchRowsScanned(page.items.length);
|
|
3780
|
+
for (const row of page.items) {
|
|
3781
|
+
if (!checkPackagePrefix(row.filePath, scope?.packagePrefix)) {
|
|
3782
|
+
continue;
|
|
3783
|
+
}
|
|
3784
|
+
if (glob && !glob.test(row.filePath)) {
|
|
3785
|
+
continue;
|
|
3786
|
+
}
|
|
3711
3787
|
const contentIndex = match === "regex"
|
|
3712
3788
|
? matchRegexIndex(row.content, regexPattern)
|
|
3713
3789
|
: findContentMatchIndex(row.content, query, match);
|
|
@@ -3721,29 +3797,44 @@ export class SourceService {
|
|
|
3721
3797
|
reasonCodes: ["content_match", `text_${match}`]
|
|
3722
3798
|
});
|
|
3723
3799
|
}
|
|
3800
|
+
if (!page.nextCursor) {
|
|
3801
|
+
break;
|
|
3802
|
+
}
|
|
3803
|
+
cursor = page.nextCursor;
|
|
3724
3804
|
}
|
|
3725
3805
|
}
|
|
3726
3806
|
searchPathIntent(artifactId, query, match, scope, regexPattern, onHit) {
|
|
3727
|
-
const filePaths = this.loadScopedFilePaths(artifactId, scope);
|
|
3728
|
-
const matching = filePaths.flatMap((filePath) => {
|
|
3729
|
-
const pathIndex = match === "regex"
|
|
3730
|
-
? matchRegexIndex(filePath, regexPattern)
|
|
3731
|
-
: findMatchIndex(filePath, query, match);
|
|
3732
|
-
if (pathIndex < 0) {
|
|
3733
|
-
return [];
|
|
3734
|
-
}
|
|
3735
|
-
return [{ filePath, pathIndex }];
|
|
3736
|
-
});
|
|
3737
3807
|
const pageSize = Math.max(1, this.config.searchScanPageSize ?? 250);
|
|
3738
|
-
|
|
3739
|
-
|
|
3808
|
+
const glob = scope?.fileGlob ? buildGlobRegex(normalizePathStyle(scope.fileGlob)) : undefined;
|
|
3809
|
+
let cursor = undefined;
|
|
3810
|
+
while (true) {
|
|
3811
|
+
const page = this.filesRepo.listFiles(artifactId, { limit: pageSize, cursor });
|
|
3812
|
+
this.metrics.recordSearchDbRoundtrip();
|
|
3813
|
+
this.metrics.recordSearchRowsScanned(page.items.length);
|
|
3814
|
+
for (const filePath of page.items) {
|
|
3815
|
+
if (!checkPackagePrefix(filePath, scope?.packagePrefix)) {
|
|
3816
|
+
continue;
|
|
3817
|
+
}
|
|
3818
|
+
if (glob && !glob.test(filePath)) {
|
|
3819
|
+
continue;
|
|
3820
|
+
}
|
|
3821
|
+
const pathIndex = match === "regex"
|
|
3822
|
+
? matchRegexIndex(filePath, regexPattern)
|
|
3823
|
+
: findMatchIndex(filePath, query, match);
|
|
3824
|
+
if (pathIndex < 0) {
|
|
3825
|
+
continue;
|
|
3826
|
+
}
|
|
3740
3827
|
onHit({
|
|
3741
|
-
filePath
|
|
3742
|
-
score: scorePathMatch(match,
|
|
3828
|
+
filePath,
|
|
3829
|
+
score: scorePathMatch(match, pathIndex),
|
|
3743
3830
|
matchedIn: "path",
|
|
3744
3831
|
reasonCodes: ["path_match", `path_${match}`]
|
|
3745
3832
|
});
|
|
3746
3833
|
}
|
|
3834
|
+
if (!page.nextCursor) {
|
|
3835
|
+
break;
|
|
3836
|
+
}
|
|
3837
|
+
cursor = page.nextCursor;
|
|
3747
3838
|
}
|
|
3748
3839
|
}
|
|
3749
3840
|
findSymbolHits(artifactId, query, match, scope, regexPattern) {
|
|
@@ -3806,31 +3897,6 @@ export class SourceService {
|
|
|
3806
3897
|
}
|
|
3807
3898
|
return result;
|
|
3808
3899
|
}
|
|
3809
|
-
loadScopedFilePaths(artifactId, scope) {
|
|
3810
|
-
const glob = scope?.fileGlob ? buildGlobRegex(normalizePathStyle(scope.fileGlob)) : undefined;
|
|
3811
|
-
const result = [];
|
|
3812
|
-
let cursor = undefined;
|
|
3813
|
-
const pageSize = Math.max(1, this.config.searchScanPageSize ?? 250);
|
|
3814
|
-
while (true) {
|
|
3815
|
-
const page = this.filesRepo.listFiles(artifactId, { limit: pageSize, cursor });
|
|
3816
|
-
this.metrics.recordSearchDbRoundtrip();
|
|
3817
|
-
this.metrics.recordSearchRowsScanned(page.items.length);
|
|
3818
|
-
for (const filePath of page.items) {
|
|
3819
|
-
if (!checkPackagePrefix(filePath, scope?.packagePrefix)) {
|
|
3820
|
-
continue;
|
|
3821
|
-
}
|
|
3822
|
-
if (glob && !glob.test(filePath)) {
|
|
3823
|
-
continue;
|
|
3824
|
-
}
|
|
3825
|
-
result.push(filePath);
|
|
3826
|
-
}
|
|
3827
|
-
if (!page.nextCursor) {
|
|
3828
|
-
break;
|
|
3829
|
-
}
|
|
3830
|
-
cursor = page.nextCursor;
|
|
3831
|
-
}
|
|
3832
|
-
return result;
|
|
3833
|
-
}
|
|
3834
3900
|
indexedCandidateLimit() {
|
|
3835
3901
|
return Math.min(Math.max(this.config.maxSearchHits * 5, 500), 5000);
|
|
3836
3902
|
}
|
|
@@ -4051,6 +4117,38 @@ export class SourceService {
|
|
|
4051
4117
|
details
|
|
4052
4118
|
});
|
|
4053
4119
|
}
|
|
4120
|
+
rejectLifecycleClassLikeInput(input) {
|
|
4121
|
+
if (!looksLikeClassSegment(input.methodName)) {
|
|
4122
|
+
return;
|
|
4123
|
+
}
|
|
4124
|
+
const classLikeSymbol = `${input.className}.${input.methodName}`;
|
|
4125
|
+
throw createError({
|
|
4126
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
4127
|
+
message: `symbol must be in the form "fully.qualified.Class.method".`,
|
|
4128
|
+
details: {
|
|
4129
|
+
symbol: input.symbol,
|
|
4130
|
+
classLikeSymbol,
|
|
4131
|
+
nextAction: "Pass lifecycle input as Class.method and use the separate descriptor field for exact overload matching.",
|
|
4132
|
+
suggestedCall: input.version
|
|
4133
|
+
? {
|
|
4134
|
+
tool: "check-symbol-exists",
|
|
4135
|
+
params: {
|
|
4136
|
+
version: input.version,
|
|
4137
|
+
kind: "class",
|
|
4138
|
+
name: classLikeSymbol,
|
|
4139
|
+
sourceMapping: input.mapping
|
|
4140
|
+
}
|
|
4141
|
+
}
|
|
4142
|
+
: undefined
|
|
4143
|
+
}
|
|
4144
|
+
});
|
|
4145
|
+
}
|
|
4146
|
+
releaseLifecycleMappingGraph(version, sourcePriority) {
|
|
4147
|
+
if ("releaseGraphCacheEntry" in this.mappingService &&
|
|
4148
|
+
typeof this.mappingService.releaseGraphCacheEntry === "function") {
|
|
4149
|
+
this.mappingService.releaseGraphCacheEntry(version, sourcePriority);
|
|
4150
|
+
}
|
|
4151
|
+
}
|
|
4054
4152
|
async resolveToObfuscatedClassName(className, version, mapping, sourcePriority, warnings) {
|
|
4055
4153
|
return this.resolveClassNameForLookup({
|
|
4056
4154
|
className,
|
|
@@ -4543,11 +4641,7 @@ export class SourceService {
|
|
|
4543
4641
|
publishCacheMetrics() {
|
|
4544
4642
|
this.metrics.setCacheEntries(this.cacheMetricsState.entries);
|
|
4545
4643
|
this.metrics.setCacheTotalContentBytes(this.cacheMetricsState.totalContentBytes);
|
|
4546
|
-
this.metrics.
|
|
4547
|
-
artifact_id: row.artifactId,
|
|
4548
|
-
content_bytes: row.totalContentBytes,
|
|
4549
|
-
updated_at: row.updatedAt
|
|
4550
|
-
})));
|
|
4644
|
+
this.metrics.setCacheArtifactByteAccountingRef(this.cacheMetricsState.lru);
|
|
4551
4645
|
}
|
|
4552
4646
|
}
|
|
4553
4647
|
//# sourceMappingURL=source-service.js.map
|
|
@@ -102,6 +102,25 @@ function buildPreview(content, query) {
|
|
|
102
102
|
const suffix = end < content.length ? "..." : "";
|
|
103
103
|
return `${prefix}${content.slice(start, end)}${suffix}`.replace(/\s+/g, " ").trim();
|
|
104
104
|
}
|
|
105
|
+
function tokenizeIndexedQuery(query) {
|
|
106
|
+
return query.match(/[\p{L}\p{N}]+/gu) ?? [];
|
|
107
|
+
}
|
|
108
|
+
function buildIndexedMatchQuery(query, match) {
|
|
109
|
+
const tokens = tokenizeIndexedQuery(query.trim());
|
|
110
|
+
if (tokens.length === 0) {
|
|
111
|
+
return query.trim();
|
|
112
|
+
}
|
|
113
|
+
// Separator-heavy queries become an order-agnostic FTS5 MATCH expression.
|
|
114
|
+
// Whitespace is implicit AND, which keeps "dispatcher.register" on the indexed
|
|
115
|
+
// path while SourceService re-checks hydrated path/content matches before
|
|
116
|
+
// returning hits. Callers can still use literal mode for exact substring scans.
|
|
117
|
+
return tokens.map((token, index) => {
|
|
118
|
+
if (match === "prefix" && index === tokens.length - 1) {
|
|
119
|
+
return `${token}*`;
|
|
120
|
+
}
|
|
121
|
+
return token;
|
|
122
|
+
}).join(" ");
|
|
123
|
+
}
|
|
105
124
|
export class FilesRepo {
|
|
106
125
|
db;
|
|
107
126
|
deleteStmt;
|
|
@@ -257,6 +276,7 @@ export class FilesRepo {
|
|
|
257
276
|
}
|
|
258
277
|
const cursor = parseSearchCursor(options.cursor);
|
|
259
278
|
const likeQuery = `%${normalized}%`;
|
|
279
|
+
const ftsQuery = buildIndexedMatchQuery(normalized, options.match);
|
|
260
280
|
const mode = options.mode ?? "mixed";
|
|
261
281
|
// Cursor-adaptive fetch limit: when no cursor, use a generous limit;
|
|
262
282
|
// with cursor + SQL pushdown, we need far fewer rows.
|
|
@@ -294,9 +314,9 @@ export class FilesRepo {
|
|
|
294
314
|
}));
|
|
295
315
|
const mergedByPath = new Map(merged.map((hit) => [hit.filePath, hit]));
|
|
296
316
|
let contentRows = [];
|
|
297
|
-
if (includeContent) {
|
|
317
|
+
if (includeContent && ftsQuery) {
|
|
298
318
|
try {
|
|
299
|
-
contentRows = this.searchFtsStmt.all(artifactId,
|
|
319
|
+
contentRows = this.searchFtsStmt.all(artifactId, ftsQuery, fetchLimit);
|
|
300
320
|
}
|
|
301
321
|
catch (error) {
|
|
302
322
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -305,7 +325,7 @@ export class FilesRepo {
|
|
|
305
325
|
}
|
|
306
326
|
log("warn", "storage.files.fts_syntax_error", {
|
|
307
327
|
artifactId,
|
|
308
|
-
query:
|
|
328
|
+
query: ftsQuery,
|
|
309
329
|
message
|
|
310
330
|
});
|
|
311
331
|
}
|
|
@@ -379,14 +399,18 @@ export class FilesRepo {
|
|
|
379
399
|
if (!normalized) {
|
|
380
400
|
return 0;
|
|
381
401
|
}
|
|
402
|
+
const ftsQuery = buildIndexedMatchQuery(normalized, "contains");
|
|
403
|
+
if (!ftsQuery) {
|
|
404
|
+
return 0;
|
|
405
|
+
}
|
|
382
406
|
try {
|
|
383
|
-
const row = this.db.prepare(`SELECT COUNT(*) AS cnt FROM files_fts WHERE artifact_id = ? AND files_fts MATCH ?`).get(artifactId,
|
|
407
|
+
const row = this.db.prepare(`SELECT COUNT(*) AS cnt FROM files_fts WHERE artifact_id = ? AND files_fts MATCH ?`).get(artifactId, ftsQuery);
|
|
384
408
|
return row?.cnt ?? 0;
|
|
385
409
|
}
|
|
386
410
|
catch {
|
|
387
411
|
log("warn", "storage.files.count_text_candidates_failed", {
|
|
388
412
|
artifactId,
|
|
389
|
-
query:
|
|
413
|
+
query: ftsQuery
|
|
390
414
|
});
|
|
391
415
|
return 0;
|
|
392
416
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const TOOL_SURFACE_SECTION_IDS: readonly ["v3-entry-tools", "source-exploration", "version-comparison-symbol-tracking", "mapping-symbols", "nbt-utilities", "mod-analysis", "validation", "registry-diagnostics"];
|
|
2
|
+
export type ToolSurfaceSectionId = (typeof TOOL_SURFACE_SECTION_IDS)[number];
|
|
3
|
+
export type ToolSurfaceLocale = "en" | "ja";
|
|
4
|
+
export declare function renderToolSurfaceSection(locale: ToolSurfaceLocale, sectionId: ToolSurfaceSectionId): string;
|