@adhisang/minecraft-modding-mcp 2.0.0 → 3.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 +62 -0
- package/README.md +139 -30
- package/dist/cache-registry.d.ts +95 -0
- package/dist/cache-registry.js +541 -0
- package/dist/cli.js +31 -4
- package/dist/compat-stdio-transport.d.ts +2 -7
- package/dist/compat-stdio-transport.js +12 -154
- package/dist/entry-tools/analyze-mod-service.d.ts +207 -0
- package/dist/entry-tools/analyze-mod-service.js +253 -0
- package/dist/entry-tools/analyze-symbol-service.d.ts +209 -0
- package/dist/entry-tools/analyze-symbol-service.js +304 -0
- package/dist/entry-tools/compare-minecraft-service.d.ts +210 -0
- package/dist/entry-tools/compare-minecraft-service.js +397 -0
- package/dist/entry-tools/entry-tool-schema.d.ts +6 -0
- package/dist/entry-tools/entry-tool-schema.js +10 -0
- package/dist/entry-tools/inspect-minecraft-service.d.ts +1953 -0
- package/dist/entry-tools/inspect-minecraft-service.js +876 -0
- package/dist/entry-tools/manage-cache-service.d.ts +130 -0
- package/dist/entry-tools/manage-cache-service.js +229 -0
- package/dist/entry-tools/request-normalizers.d.ts +10 -0
- package/dist/entry-tools/request-normalizers.js +36 -0
- package/dist/entry-tools/response-contract.d.ts +44 -0
- package/dist/entry-tools/response-contract.js +96 -0
- package/dist/entry-tools/validate-project-service.d.ts +543 -0
- package/dist/entry-tools/validate-project-service.js +381 -0
- package/dist/index.js +495 -42
- package/dist/json-rpc-framing.d.ts +22 -0
- package/dist/json-rpc-framing.js +168 -0
- package/dist/mapping-pipeline-service.js +9 -1
- package/dist/mapping-service.d.ts +9 -0
- package/dist/mapping-service.js +183 -60
- package/dist/minecraft-explorer-service.d.ts +0 -1
- package/dist/minecraft-explorer-service.js +119 -23
- package/dist/mixin-validator.d.ts +24 -2
- package/dist/mixin-validator.js +223 -98
- package/dist/mod-decompile-service.d.ts +5 -0
- package/dist/mod-decompile-service.js +40 -5
- package/dist/mod-remap-service.js +142 -30
- package/dist/path-resolver.js +41 -4
- package/dist/registry-service.d.ts +10 -1
- package/dist/registry-service.js +154 -22
- package/dist/search-hit-accumulator.js +23 -2
- package/dist/source-jar-reader.js +16 -2
- package/dist/source-resolver.js +6 -7
- package/dist/source-service.d.ts +42 -4
- package/dist/source-service.js +781 -127
- package/dist/stdio-supervisor.d.ts +46 -0
- package/dist/stdio-supervisor.js +349 -0
- package/dist/storage/files-repo.d.ts +3 -9
- package/dist/storage/files-repo.js +66 -43
- package/dist/symbols/symbol-extractor.js +6 -4
- package/dist/tool-execution-gate.d.ts +15 -0
- package/dist/tool-execution-gate.js +58 -0
- package/dist/version-diff-service.js +10 -5
- package/dist/version-service.js +7 -2
- package/dist/workspace-mapping-service.js +12 -0
- package/package.json +1 -1
package/dist/source-service.js
CHANGED
|
@@ -12,7 +12,7 @@ import { parseCoordinate } from "./maven-resolver.js";
|
|
|
12
12
|
import { MinecraftExplorerService } from "./minecraft-explorer-service.js";
|
|
13
13
|
import { parseMixinSource } from "./mixin-parser.js";
|
|
14
14
|
import { parseAccessWidener } from "./access-widener-parser.js";
|
|
15
|
-
import { validateParsedMixin, validateParsedAccessWidener } from "./mixin-validator.js";
|
|
15
|
+
import { validateParsedMixin, refreshMixinValidationOutcome, validateParsedAccessWidener } from "./mixin-validator.js";
|
|
16
16
|
import { resolveSourceTarget as resolveSourceTargetInternal } from "./source-resolver.js";
|
|
17
17
|
import { applyMappingPipeline } from "./mapping-pipeline-service.js";
|
|
18
18
|
import { MappingService } from "./mapping-service.js";
|
|
@@ -34,6 +34,19 @@ import { VersionDiffService } from "./version-diff-service.js";
|
|
|
34
34
|
import { ModDecompileService } from "./mod-decompile-service.js";
|
|
35
35
|
import { ModSearchService } from "./mod-search-service.js";
|
|
36
36
|
const utf8Decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
37
|
+
const VERSION_TOKEN_REGEX_CACHE = new Map();
|
|
38
|
+
const GLOB_REGEX_CACHE = new Map();
|
|
39
|
+
const MAX_HELPER_REGEX_CACHE = 128;
|
|
40
|
+
function rememberCachedRegex(cache, key, regex) {
|
|
41
|
+
if (cache.size >= MAX_HELPER_REGEX_CACHE) {
|
|
42
|
+
const oldestKey = cache.keys().next().value;
|
|
43
|
+
if (oldestKey) {
|
|
44
|
+
cache.delete(oldestKey);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
cache.set(key, regex);
|
|
48
|
+
return regex;
|
|
49
|
+
}
|
|
37
50
|
function truncateUtf8ToMaxBytes(content, maxBytes) {
|
|
38
51
|
const encoded = Buffer.from(content, "utf8");
|
|
39
52
|
if (encoded.length <= maxBytes) {
|
|
@@ -51,6 +64,88 @@ function truncateUtf8ToMaxBytes(content, maxBytes) {
|
|
|
51
64
|
}
|
|
52
65
|
return "";
|
|
53
66
|
}
|
|
67
|
+
function dedupeQualityFlags(qualityFlags) {
|
|
68
|
+
const seen = new Set();
|
|
69
|
+
const deduped = [];
|
|
70
|
+
for (const qualityFlag of qualityFlags) {
|
|
71
|
+
if (seen.has(qualityFlag)) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
seen.add(qualityFlag);
|
|
75
|
+
deduped.push(qualityFlag);
|
|
76
|
+
}
|
|
77
|
+
return deduped;
|
|
78
|
+
}
|
|
79
|
+
function sameStringArray(left, right) {
|
|
80
|
+
if (left === right) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
if (!left || !right || left.length !== right.length) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
87
|
+
if (left[index] !== right[index]) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
function sameScopeFallback(left, right) {
|
|
94
|
+
if (left === right) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
if (!left || !right) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
return left.requested === right.requested && left.applied === right.applied && left.reason === right.reason;
|
|
101
|
+
}
|
|
102
|
+
function sameResolutionTrace(left, right) {
|
|
103
|
+
if (left === right) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
if (!left || !right || left.length !== right.length) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
110
|
+
const leftEntry = left[index];
|
|
111
|
+
const rightEntry = right[index];
|
|
112
|
+
if (!leftEntry || !rightEntry) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
if (leftEntry.target !== rightEntry.target ||
|
|
116
|
+
leftEntry.step !== rightEntry.step ||
|
|
117
|
+
leftEntry.input !== rightEntry.input ||
|
|
118
|
+
leftEntry.output !== rightEntry.output ||
|
|
119
|
+
leftEntry.success !== rightEntry.success ||
|
|
120
|
+
leftEntry.detail !== rightEntry.detail) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
function sameMixinValidationProvenance(left, right) {
|
|
127
|
+
if (left === right) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
if (!left || !right) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
return (left.version === right.version &&
|
|
134
|
+
left.jarPath === right.jarPath &&
|
|
135
|
+
left.requestedMapping === right.requestedMapping &&
|
|
136
|
+
left.mappingApplied === right.mappingApplied &&
|
|
137
|
+
left.requestedScope === right.requestedScope &&
|
|
138
|
+
left.appliedScope === right.appliedScope &&
|
|
139
|
+
left.requestedSourcePriority === right.requestedSourcePriority &&
|
|
140
|
+
left.appliedSourcePriority === right.appliedSourcePriority &&
|
|
141
|
+
sameStringArray(left.resolutionNotes, right.resolutionNotes) &&
|
|
142
|
+
left.jarType === right.jarType &&
|
|
143
|
+
sameStringArray(left.mappingChain, right.mappingChain) &&
|
|
144
|
+
left.remapFailures === right.remapFailures &&
|
|
145
|
+
left.mappingAutoDetected === right.mappingAutoDetected &&
|
|
146
|
+
sameScopeFallback(left.scopeFallback, right.scopeFallback) &&
|
|
147
|
+
sameResolutionTrace(left.resolutionTrace, right.resolutionTrace));
|
|
148
|
+
}
|
|
54
149
|
const INDEX_SCHEMA_VERSION = 1;
|
|
55
150
|
const SYMBOL_KINDS = ["class", "interface", "enum", "record", "method", "field"];
|
|
56
151
|
function isSymbolKind(value) {
|
|
@@ -77,7 +172,9 @@ function hasExactVersionToken(path, version) {
|
|
|
77
172
|
return false;
|
|
78
173
|
}
|
|
79
174
|
// Avoid prefix false-positives like "1.21.1" matching "1.21.10".
|
|
80
|
-
const
|
|
175
|
+
const cached = VERSION_TOKEN_REGEX_CACHE.get(normalizedVersion);
|
|
176
|
+
const pattern = cached
|
|
177
|
+
?? rememberCachedRegex(VERSION_TOKEN_REGEX_CACHE, normalizedVersion, new RegExp(`(^|[^0-9a-z])${escapeRegexLiteral(normalizedVersion)}([^0-9a-z]|$)`, "i"));
|
|
81
178
|
return pattern.test(normalizedPath);
|
|
82
179
|
}
|
|
83
180
|
function looksLikeDeobfuscatedClassName(value) {
|
|
@@ -97,6 +194,15 @@ function obfuscatedNamespaceHint(className) {
|
|
|
97
194
|
function hasPartialNetMinecraftCoverage(qualityFlags) {
|
|
98
195
|
return qualityFlags.includes("partial-source-no-net-minecraft");
|
|
99
196
|
}
|
|
197
|
+
function buildResolveArtifactParams(target, extra = {}) {
|
|
198
|
+
return {
|
|
199
|
+
target: {
|
|
200
|
+
kind: target.kind,
|
|
201
|
+
value: target.value
|
|
202
|
+
},
|
|
203
|
+
...extra
|
|
204
|
+
};
|
|
205
|
+
}
|
|
100
206
|
function normalizeOptionalProjectPath(projectPath) {
|
|
101
207
|
if (!projectPath) {
|
|
102
208
|
return undefined;
|
|
@@ -108,19 +214,68 @@ function normalizeOptionalProjectPath(projectPath) {
|
|
|
108
214
|
const normalized = normalizePathForHost(trimmed, undefined, "projectPath");
|
|
109
215
|
return isAbsolute(normalized) ? normalized : resolvePath(process.cwd(), normalized);
|
|
110
216
|
}
|
|
217
|
+
function resolveGradleUserHomePath() {
|
|
218
|
+
const configured = process.env.GRADLE_USER_HOME?.trim();
|
|
219
|
+
if (!configured) {
|
|
220
|
+
return resolvePath(homedir(), ".gradle");
|
|
221
|
+
}
|
|
222
|
+
const normalized = normalizePathForHost(configured, undefined, "GRADLE_USER_HOME");
|
|
223
|
+
return isAbsolute(normalized) ? normalized : resolvePath(process.cwd(), normalized);
|
|
224
|
+
}
|
|
111
225
|
function buildVersionSourceSearchRoots(projectPath) {
|
|
112
226
|
const roots = new Set();
|
|
113
227
|
if (projectPath) {
|
|
114
228
|
roots.add(resolvePath(projectPath, ".gradle", "loom-cache"));
|
|
115
229
|
roots.add(resolvePath(projectPath, ".gradle-user", "caches", "fabric-loom"));
|
|
116
230
|
roots.add(resolvePath(projectPath, ".gradle", "caches", "fabric-loom"));
|
|
117
|
-
|
|
231
|
+
const projectParent = dirname(projectPath);
|
|
232
|
+
roots.add(resolvePath(projectParent, ".gradle-user-home", "loom-cache"));
|
|
233
|
+
roots.add(resolvePath(projectParent, ".gradle-user-home", "caches", "fabric-loom"));
|
|
118
234
|
}
|
|
119
|
-
const homeGradle =
|
|
235
|
+
const homeGradle = resolveGradleUserHomePath();
|
|
120
236
|
roots.add(resolvePath(homeGradle, "loom-cache"));
|
|
121
237
|
roots.add(resolvePath(homeGradle, "caches", "fabric-loom"));
|
|
122
238
|
return [...roots];
|
|
123
239
|
}
|
|
240
|
+
function looksLikeMinecraftSourceArtifact(path, hasMinecraftNamespace) {
|
|
241
|
+
if (hasMinecraftNamespace) {
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
const normalizedPath = normalizePathStyle(path).toLowerCase();
|
|
245
|
+
return (normalizedPath.includes("/minecraftmaven/") ||
|
|
246
|
+
normalizedPath.includes("/net/minecraft/") ||
|
|
247
|
+
/(?:^|\/)minecraft(?:-[a-z0-9._+]+)*-sources\.jar$/i.test(normalizedPath) ||
|
|
248
|
+
normalizedPath.includes("minecraft-merged") ||
|
|
249
|
+
normalizedPath.includes("minecraft-common") ||
|
|
250
|
+
normalizedPath.includes("minecraft-clientonly") ||
|
|
251
|
+
normalizedPath.includes("minecraft-client") ||
|
|
252
|
+
normalizedPath.includes("minecraft-server"));
|
|
253
|
+
}
|
|
254
|
+
function normalizeRequestedArtifactScope(scope) {
|
|
255
|
+
return scope ?? "vanilla";
|
|
256
|
+
}
|
|
257
|
+
function inferAppliedArtifactScope(input) {
|
|
258
|
+
if (input.scopeFallback?.applied === "vanilla") {
|
|
259
|
+
return "vanilla";
|
|
260
|
+
}
|
|
261
|
+
if (input.requestedScope === "vanilla") {
|
|
262
|
+
return "vanilla";
|
|
263
|
+
}
|
|
264
|
+
const joinedPath = `${normalizePathStyle(input.jarPath)} ${normalizePathStyle(input.resolvedSourceJarPath ?? "")}`.toLowerCase();
|
|
265
|
+
if (joinedPath.includes("minecraft-merged")) {
|
|
266
|
+
return "merged";
|
|
267
|
+
}
|
|
268
|
+
if (input.requestedScope === "loader" && joinedPath.includes("merged")) {
|
|
269
|
+
return "merged";
|
|
270
|
+
}
|
|
271
|
+
return input.requestedScope;
|
|
272
|
+
}
|
|
273
|
+
function scopeToJarType(scope) {
|
|
274
|
+
if (scope === "vanilla") {
|
|
275
|
+
return "vanilla-client";
|
|
276
|
+
}
|
|
277
|
+
return scope;
|
|
278
|
+
}
|
|
124
279
|
function parseQualifiedMethodSymbol(symbol) {
|
|
125
280
|
const trimmed = symbol.trim();
|
|
126
281
|
const separator = trimmed.lastIndexOf(".");
|
|
@@ -183,6 +338,13 @@ const COMMON_SOURCE_ROOTS = [
|
|
|
183
338
|
"quilt/src/main/java",
|
|
184
339
|
"quilt/src/client/java"
|
|
185
340
|
];
|
|
341
|
+
const MIXIN_PROJECT_DISCOVERY_IGNORES = [
|
|
342
|
+
"**/.git/**",
|
|
343
|
+
"**/.gradle/**",
|
|
344
|
+
"**/build/**",
|
|
345
|
+
"**/out/**",
|
|
346
|
+
"**/node_modules/**"
|
|
347
|
+
];
|
|
186
348
|
function normalizeMapping(mapping) {
|
|
187
349
|
if (mapping == null) {
|
|
188
350
|
return "obfuscated";
|
|
@@ -251,11 +413,11 @@ function sortDiffMemberChanges(changes) {
|
|
|
251
413
|
if (keyCompare !== 0) {
|
|
252
414
|
return keyCompare;
|
|
253
415
|
}
|
|
254
|
-
const fromOwnerCompare = left.from
|
|
416
|
+
const fromOwnerCompare = (left.from?.ownerFqn ?? "").localeCompare(right.from?.ownerFqn ?? "");
|
|
255
417
|
if (fromOwnerCompare !== 0) {
|
|
256
418
|
return fromOwnerCompare;
|
|
257
419
|
}
|
|
258
|
-
return left.to
|
|
420
|
+
return (left.to?.ownerFqn ?? "").localeCompare(right.to?.ownerFqn ?? "");
|
|
259
421
|
});
|
|
260
422
|
}
|
|
261
423
|
function changedMemberFields(fromMember, toMember, includeDescriptor) {
|
|
@@ -328,6 +490,16 @@ function emptyDiffDelta() {
|
|
|
328
490
|
modified: []
|
|
329
491
|
};
|
|
330
492
|
}
|
|
493
|
+
function compactDiffDelta(delta) {
|
|
494
|
+
return {
|
|
495
|
+
added: delta.added,
|
|
496
|
+
removed: delta.removed,
|
|
497
|
+
modified: delta.modified.map((change) => ({
|
|
498
|
+
key: change.key,
|
|
499
|
+
changed: [...change.changed]
|
|
500
|
+
}))
|
|
501
|
+
};
|
|
502
|
+
}
|
|
331
503
|
function normalizeIntent(intent) {
|
|
332
504
|
if (intent === "path" || intent === "text") {
|
|
333
505
|
return intent;
|
|
@@ -354,6 +526,12 @@ function canUseIndexedSearchPath(indexedSearchEnabled, intent, match, _scope) {
|
|
|
354
526
|
return true;
|
|
355
527
|
}
|
|
356
528
|
function buildGlobRegex(pattern) {
|
|
529
|
+
const cached = GLOB_REGEX_CACHE.get(pattern);
|
|
530
|
+
if (cached) {
|
|
531
|
+
GLOB_REGEX_CACHE.delete(pattern);
|
|
532
|
+
GLOB_REGEX_CACHE.set(pattern, cached);
|
|
533
|
+
return cached;
|
|
534
|
+
}
|
|
357
535
|
const REGEX_META = /[-/\\^$+.()|[\]{}]/;
|
|
358
536
|
let result = "";
|
|
359
537
|
let i = 0;
|
|
@@ -380,7 +558,7 @@ function buildGlobRegex(pattern) {
|
|
|
380
558
|
i += 1;
|
|
381
559
|
}
|
|
382
560
|
}
|
|
383
|
-
return new RegExp(`^${result}$`);
|
|
561
|
+
return rememberCachedRegex(GLOB_REGEX_CACHE, pattern, new RegExp(`^${result}$`));
|
|
384
562
|
}
|
|
385
563
|
function globToSqlLike(pattern) {
|
|
386
564
|
let result = "";
|
|
@@ -556,6 +734,11 @@ export class SourceService {
|
|
|
556
734
|
versionDiffService;
|
|
557
735
|
modDecompileService;
|
|
558
736
|
modSearchService;
|
|
737
|
+
cacheMetricsState = {
|
|
738
|
+
entries: 0,
|
|
739
|
+
totalContentBytes: 0,
|
|
740
|
+
lru: []
|
|
741
|
+
};
|
|
559
742
|
constructor(explicitConfig, metrics = new RuntimeMetrics()) {
|
|
560
743
|
this.config = explicitConfig ?? loadConfig();
|
|
561
744
|
this.metrics = metrics;
|
|
@@ -615,14 +798,18 @@ export class SourceService {
|
|
|
615
798
|
continue;
|
|
616
799
|
}
|
|
617
800
|
const hasMinecraftNamespace = javaEntries.some((entry) => normalizePathStyle(entry).startsWith("net/minecraft/"));
|
|
618
|
-
const
|
|
801
|
+
const looksLikeMinecraftArtifact = looksLikeMinecraftSourceArtifact(normalizedPath, hasMinecraftNamespace);
|
|
802
|
+
const exactVersionMatch = hasExactVersionToken(normalizedPath, input.version);
|
|
803
|
+
const score = (looksLikeMinecraftArtifact ? 20_000 : 0) +
|
|
804
|
+
(hasMinecraftNamespace ? 10_000 : 0) +
|
|
619
805
|
(lower.includes("minecraft-merged") ? 2_000 : 0) +
|
|
620
|
-
(
|
|
806
|
+
(exactVersionMatch ? 1_000 : 0) +
|
|
621
807
|
Math.min(javaEntries.length, 500);
|
|
622
808
|
candidates.push({
|
|
623
809
|
jarPath: normalizedPath,
|
|
624
810
|
javaEntryCount: javaEntries.length,
|
|
625
811
|
hasMinecraftNamespace,
|
|
812
|
+
looksLikeMinecraftArtifact,
|
|
626
813
|
score
|
|
627
814
|
});
|
|
628
815
|
}
|
|
@@ -633,7 +820,8 @@ export class SourceService {
|
|
|
633
820
|
}
|
|
634
821
|
return left.jarPath.localeCompare(right.jarPath);
|
|
635
822
|
});
|
|
636
|
-
const selected = candidates.find((candidate) => candidate.hasMinecraftNamespace) ??
|
|
823
|
+
const selected = candidates.find((candidate) => candidate.looksLikeMinecraftArtifact && candidate.hasMinecraftNamespace) ??
|
|
824
|
+
candidates.find((candidate) => candidate.looksLikeMinecraftArtifact);
|
|
637
825
|
const candidateArtifacts = candidates
|
|
638
826
|
.slice(0, 20)
|
|
639
827
|
.map((candidate) => `${candidate.jarPath}#java=${candidate.javaEntryCount}#net_minecraft=${candidate.hasMinecraftNamespace ? 1 : 0}`);
|
|
@@ -651,6 +839,58 @@ export class SourceService {
|
|
|
651
839
|
: "";
|
|
652
840
|
return `${prefix}./gradlew genSources --no-daemon`;
|
|
653
841
|
}
|
|
842
|
+
buildArtifactContentsSummary(input) {
|
|
843
|
+
const sourceKind = input.isDecompiled || input.origin === "decompiled" || !normalizeOptionalString(input.sourceJarPath)
|
|
844
|
+
? "decompiled-binary"
|
|
845
|
+
: "source-jar";
|
|
846
|
+
const sourceCoverage = hasPartialNetMinecraftCoverage(input.qualityFlags) ? "partial" : "full";
|
|
847
|
+
return {
|
|
848
|
+
sourceKind,
|
|
849
|
+
indexedContentKinds: ["java-source"],
|
|
850
|
+
resourcesIncluded: false,
|
|
851
|
+
sourceCoverage
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
inferVersionFromContext(input) {
|
|
855
|
+
const direct = normalizeOptionalString(input.version);
|
|
856
|
+
if (direct) {
|
|
857
|
+
return direct;
|
|
858
|
+
}
|
|
859
|
+
const resolvedFromVersion = normalizeOptionalString(input.provenance?.resolvedFrom.version);
|
|
860
|
+
if (resolvedFromVersion) {
|
|
861
|
+
return resolvedFromVersion;
|
|
862
|
+
}
|
|
863
|
+
if (input.provenance?.target.kind === "version") {
|
|
864
|
+
const targetVersion = normalizeOptionalString(input.provenance.target.value);
|
|
865
|
+
if (targetVersion) {
|
|
866
|
+
return targetVersion;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
const coordinate = normalizeOptionalString(input.coordinate);
|
|
870
|
+
if (coordinate) {
|
|
871
|
+
try {
|
|
872
|
+
return parseCoordinate(coordinate).version;
|
|
873
|
+
}
|
|
874
|
+
catch {
|
|
875
|
+
return undefined;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
return undefined;
|
|
879
|
+
}
|
|
880
|
+
async resolveVersionContext(input) {
|
|
881
|
+
const inferredVersion = this.inferVersionFromContext(input);
|
|
882
|
+
if (inferredVersion) {
|
|
883
|
+
return inferredVersion;
|
|
884
|
+
}
|
|
885
|
+
if (!input.preferProjectVersion || !input.projectPath) {
|
|
886
|
+
return undefined;
|
|
887
|
+
}
|
|
888
|
+
const detected = await this.workspaceMappingService.detectProjectMinecraftVersion(input.projectPath);
|
|
889
|
+
if (detected) {
|
|
890
|
+
input.warnings.push(`Using project version "${detected}" from gradle.properties because the artifact metadata did not include a version.`);
|
|
891
|
+
}
|
|
892
|
+
return detected;
|
|
893
|
+
}
|
|
654
894
|
async resolveArtifact(input) {
|
|
655
895
|
const kind = input.target.kind;
|
|
656
896
|
let value = input.target.value?.trim();
|
|
@@ -762,7 +1002,7 @@ export class SourceService {
|
|
|
762
1002
|
if (isVanillaMojang && input.projectPath) {
|
|
763
1003
|
suggestedCall = {
|
|
764
1004
|
tool: "resolve-artifact",
|
|
765
|
-
params: {
|
|
1005
|
+
params: buildResolveArtifactParams({ kind, value }, { mapping: "mojang", scope: "merged", projectPath: input.projectPath })
|
|
766
1006
|
};
|
|
767
1007
|
nextAction =
|
|
768
1008
|
"scope=vanilla blocks Loom cache discovery needed for mojang mapping. " +
|
|
@@ -771,7 +1011,7 @@ export class SourceService {
|
|
|
771
1011
|
else if (isVanillaMojang) {
|
|
772
1012
|
suggestedCall = {
|
|
773
1013
|
tool: "resolve-artifact",
|
|
774
|
-
params: {
|
|
1014
|
+
params: buildResolveArtifactParams({ kind, value }, { mapping: "obfuscated", scope: "vanilla" })
|
|
775
1015
|
};
|
|
776
1016
|
nextAction =
|
|
777
1017
|
"scope=vanilla blocks Loom cache discovery needed for mojang mapping. " +
|
|
@@ -780,7 +1020,7 @@ export class SourceService {
|
|
|
780
1020
|
else {
|
|
781
1021
|
suggestedCall = {
|
|
782
1022
|
tool: "resolve-artifact",
|
|
783
|
-
params: {
|
|
1023
|
+
params: buildResolveArtifactParams({ kind, value }, { mapping: "obfuscated", ...(scope ? { scope } : {}) })
|
|
784
1024
|
};
|
|
785
1025
|
nextAction = "Retry with mapping=obfuscated to use the runtime obfuscated namespace.";
|
|
786
1026
|
}
|
|
@@ -789,6 +1029,7 @@ export class SourceService {
|
|
|
789
1029
|
message: caughtError.message,
|
|
790
1030
|
details: {
|
|
791
1031
|
...(caughtError.details ?? {}),
|
|
1032
|
+
artifactOrigin: resolved.origin,
|
|
792
1033
|
searchedPaths: versionSourceDiscovery?.searchedPaths ?? [],
|
|
793
1034
|
candidateArtifacts: versionSourceDiscovery?.candidateArtifacts ?? resolved.adjacentSourceCandidates ?? [],
|
|
794
1035
|
recommendedCommand: this.buildVersionSourceRecoveryCommand(input.projectPath),
|
|
@@ -808,8 +1049,11 @@ export class SourceService {
|
|
|
808
1049
|
details: {
|
|
809
1050
|
mapping: effectiveMapping,
|
|
810
1051
|
target: { kind, value },
|
|
811
|
-
nextAction: "Use
|
|
812
|
-
suggestedCall: {
|
|
1052
|
+
nextAction: "Use target: { kind: \"version\", value } or a versioned Maven coordinate so mapping artifacts can be resolved.",
|
|
1053
|
+
suggestedCall: {
|
|
1054
|
+
tool: "resolve-artifact",
|
|
1055
|
+
params: buildResolveArtifactParams({ kind: "version", value }, { ...(scope ? { scope } : {}) })
|
|
1056
|
+
}
|
|
813
1057
|
}
|
|
814
1058
|
});
|
|
815
1059
|
}
|
|
@@ -852,7 +1096,10 @@ export class SourceService {
|
|
|
852
1096
|
selectedSourceJar: versionSourceDiscovery.selectedSourceJarPath,
|
|
853
1097
|
candidateArtifacts: versionSourceDiscovery.candidateArtifacts,
|
|
854
1098
|
nextAction: "Use strictVersion=false (default) to allow approximation, or ensure the exact version source jar is in the Loom cache.",
|
|
855
|
-
suggestedCall: {
|
|
1099
|
+
suggestedCall: {
|
|
1100
|
+
tool: "resolve-artifact",
|
|
1101
|
+
params: buildResolveArtifactParams({ kind: "version", value }, { strictVersion: false })
|
|
1102
|
+
}
|
|
856
1103
|
}
|
|
857
1104
|
});
|
|
858
1105
|
}
|
|
@@ -860,7 +1107,7 @@ export class SourceService {
|
|
|
860
1107
|
warnings.push(`Requested version "${value}" but resolved source jar does not contain exact version string: ${versionSourceDiscovery.selectedSourceJarPath}`);
|
|
861
1108
|
}
|
|
862
1109
|
}
|
|
863
|
-
resolved.qualityFlags =
|
|
1110
|
+
resolved.qualityFlags = dedupeQualityFlags(resolved.qualityFlags);
|
|
864
1111
|
await this.ingestIfNeeded(resolved);
|
|
865
1112
|
let sampleEntries;
|
|
866
1113
|
if (resolved.sourceJarPath) {
|
|
@@ -890,6 +1137,12 @@ export class SourceService {
|
|
|
890
1137
|
provenance,
|
|
891
1138
|
qualityFlags: resolved.qualityFlags,
|
|
892
1139
|
repoUrl: resolved.repoUrl,
|
|
1140
|
+
artifactContents: this.buildArtifactContentsSummary({
|
|
1141
|
+
origin: resolved.origin,
|
|
1142
|
+
sourceJarPath: resolved.sourceJarPath,
|
|
1143
|
+
isDecompiled: resolved.isDecompiled,
|
|
1144
|
+
qualityFlags: resolved.qualityFlags
|
|
1145
|
+
}),
|
|
893
1146
|
warnings,
|
|
894
1147
|
sampleEntries
|
|
895
1148
|
};
|
|
@@ -920,7 +1173,14 @@ export class SourceService {
|
|
|
920
1173
|
if (!query) {
|
|
921
1174
|
return {
|
|
922
1175
|
hits: [],
|
|
923
|
-
mappingApplied: artifact.mappingApplied ?? "obfuscated"
|
|
1176
|
+
mappingApplied: artifact.mappingApplied ?? "obfuscated",
|
|
1177
|
+
returnedNamespace: artifact.mappingApplied ?? "obfuscated",
|
|
1178
|
+
artifactContents: this.buildArtifactContentsSummary({
|
|
1179
|
+
origin: artifact.origin,
|
|
1180
|
+
sourceJarPath: artifact.sourceJarPath,
|
|
1181
|
+
isDecompiled: artifact.isDecompiled,
|
|
1182
|
+
qualityFlags: artifact.qualityFlags
|
|
1183
|
+
})
|
|
924
1184
|
};
|
|
925
1185
|
}
|
|
926
1186
|
const intent = normalizeIntent(input.intent);
|
|
@@ -1044,7 +1304,14 @@ export class SourceService {
|
|
|
1044
1304
|
return {
|
|
1045
1305
|
hits: page,
|
|
1046
1306
|
nextCursor,
|
|
1047
|
-
mappingApplied: artifact.mappingApplied ?? "obfuscated"
|
|
1307
|
+
mappingApplied: artifact.mappingApplied ?? "obfuscated",
|
|
1308
|
+
returnedNamespace: artifact.mappingApplied ?? "obfuscated",
|
|
1309
|
+
artifactContents: this.buildArtifactContentsSummary({
|
|
1310
|
+
origin: artifact.origin,
|
|
1311
|
+
sourceJarPath: artifact.sourceJarPath,
|
|
1312
|
+
isDecompiled: artifact.isDecompiled,
|
|
1313
|
+
qualityFlags: artifact.qualityFlags
|
|
1314
|
+
})
|
|
1048
1315
|
};
|
|
1049
1316
|
}
|
|
1050
1317
|
finally {
|
|
@@ -1081,7 +1348,14 @@ export class SourceService {
|
|
|
1081
1348
|
content,
|
|
1082
1349
|
contentBytes: fullBytes,
|
|
1083
1350
|
truncated,
|
|
1084
|
-
mappingApplied: artifact.mappingApplied ?? "obfuscated"
|
|
1351
|
+
mappingApplied: artifact.mappingApplied ?? "obfuscated",
|
|
1352
|
+
returnedNamespace: artifact.mappingApplied ?? "obfuscated",
|
|
1353
|
+
artifactContents: this.buildArtifactContentsSummary({
|
|
1354
|
+
origin: artifact.origin,
|
|
1355
|
+
sourceJarPath: artifact.sourceJarPath,
|
|
1356
|
+
isDecompiled: artifact.isDecompiled,
|
|
1357
|
+
qualityFlags: artifact.qualityFlags
|
|
1358
|
+
})
|
|
1085
1359
|
};
|
|
1086
1360
|
}
|
|
1087
1361
|
finally {
|
|
@@ -1093,15 +1367,29 @@ export class SourceService {
|
|
|
1093
1367
|
try {
|
|
1094
1368
|
const artifact = this.getArtifact(input.artifactId);
|
|
1095
1369
|
const limit = clampLimit(input.limit, 200, 2000);
|
|
1370
|
+
const warnings = [];
|
|
1096
1371
|
const page = this.filesRepo.listFiles(artifact.artifactId, {
|
|
1097
1372
|
limit,
|
|
1098
1373
|
cursor: input.cursor,
|
|
1099
1374
|
prefix: input.prefix
|
|
1100
1375
|
});
|
|
1376
|
+
const normalizedPrefix = normalizeOptionalString(input.prefix);
|
|
1377
|
+
if (normalizedPrefix &&
|
|
1378
|
+
page.items.length === 0 &&
|
|
1379
|
+
(normalizedPrefix.startsWith("assets/") || normalizedPrefix.startsWith("data/"))) {
|
|
1380
|
+
warnings.push("Indexed artifacts currently include Java source only; non-Java resources are not indexed. Inspect the original jar on disk if you need assets or data files.");
|
|
1381
|
+
}
|
|
1101
1382
|
return {
|
|
1102
1383
|
items: page.items,
|
|
1103
1384
|
nextCursor: page.nextCursor,
|
|
1104
|
-
mappingApplied: artifact.mappingApplied ?? "obfuscated"
|
|
1385
|
+
mappingApplied: artifact.mappingApplied ?? "obfuscated",
|
|
1386
|
+
artifactContents: this.buildArtifactContentsSummary({
|
|
1387
|
+
origin: artifact.origin,
|
|
1388
|
+
sourceJarPath: artifact.sourceJarPath,
|
|
1389
|
+
isDecompiled: artifact.isDecompiled,
|
|
1390
|
+
qualityFlags: artifact.qualityFlags
|
|
1391
|
+
}),
|
|
1392
|
+
warnings
|
|
1105
1393
|
};
|
|
1106
1394
|
}
|
|
1107
1395
|
finally {
|
|
@@ -1240,6 +1528,7 @@ export class SourceService {
|
|
|
1240
1528
|
resolved: false,
|
|
1241
1529
|
status: "mapping_unavailable",
|
|
1242
1530
|
candidates: [],
|
|
1531
|
+
candidateCount: 0,
|
|
1243
1532
|
workspaceDetection,
|
|
1244
1533
|
warnings
|
|
1245
1534
|
};
|
|
@@ -1255,7 +1544,8 @@ export class SourceService {
|
|
|
1255
1544
|
descriptor: methodDescriptor,
|
|
1256
1545
|
sourceMapping: input.sourceMapping,
|
|
1257
1546
|
targetMapping: mappingApplied,
|
|
1258
|
-
sourcePriority: input.sourcePriority
|
|
1547
|
+
sourcePriority: input.sourcePriority,
|
|
1548
|
+
maxCandidates: input.maxCandidates
|
|
1259
1549
|
});
|
|
1260
1550
|
return {
|
|
1261
1551
|
...exact,
|
|
@@ -1285,6 +1575,7 @@ export class SourceService {
|
|
|
1285
1575
|
resolved: false,
|
|
1286
1576
|
status: "not_found",
|
|
1287
1577
|
candidates: [],
|
|
1578
|
+
candidateCount: 0,
|
|
1288
1579
|
workspaceDetection,
|
|
1289
1580
|
warnings: [...warnings, ...matrix.warnings]
|
|
1290
1581
|
};
|
|
@@ -1312,6 +1603,7 @@ export class SourceService {
|
|
|
1312
1603
|
status: "resolved",
|
|
1313
1604
|
resolvedSymbol,
|
|
1314
1605
|
candidates: [resolvedCandidate],
|
|
1606
|
+
candidateCount: 1,
|
|
1315
1607
|
workspaceDetection,
|
|
1316
1608
|
warnings: [...warnings, ...matrix.warnings]
|
|
1317
1609
|
};
|
|
@@ -1324,7 +1616,8 @@ export class SourceService {
|
|
|
1324
1616
|
descriptor,
|
|
1325
1617
|
sourceMapping: input.sourceMapping,
|
|
1326
1618
|
targetMapping: mappingApplied,
|
|
1327
|
-
sourcePriority: input.sourcePriority
|
|
1619
|
+
sourcePriority: input.sourcePriority,
|
|
1620
|
+
maxCandidates: input.maxCandidates
|
|
1328
1621
|
});
|
|
1329
1622
|
const filtered = mapped.candidates.filter((candidate) => candidate.kind === kind);
|
|
1330
1623
|
let status;
|
|
@@ -1347,6 +1640,8 @@ export class SourceService {
|
|
|
1347
1640
|
status,
|
|
1348
1641
|
resolvedSymbol: status === "resolved" ? filtered[0] : undefined,
|
|
1349
1642
|
candidates: filtered,
|
|
1643
|
+
candidateCount: mapped.candidateCount,
|
|
1644
|
+
candidatesTruncated: mapped.candidatesTruncated,
|
|
1350
1645
|
workspaceDetection,
|
|
1351
1646
|
warnings: [...warnings, ...mapped.warnings]
|
|
1352
1647
|
};
|
|
@@ -1517,6 +1812,7 @@ export class SourceService {
|
|
|
1517
1812
|
const className = input.className.trim();
|
|
1518
1813
|
const fromVersion = input.fromVersion.trim();
|
|
1519
1814
|
const toVersion = input.toVersion.trim();
|
|
1815
|
+
const includeFullDiff = input.includeFullDiff ?? true;
|
|
1520
1816
|
if (!className || !fromVersion || !toVersion) {
|
|
1521
1817
|
throw createError({
|
|
1522
1818
|
code: ERROR_CODES.INVALID_INPUT,
|
|
@@ -1690,11 +1986,39 @@ export class SourceService {
|
|
|
1690
1986
|
this.remapSignatureMembers(delta.removed, kind, fromVersion, "obfuscated", mapping, input.sourcePriority, warnings)
|
|
1691
1987
|
]);
|
|
1692
1988
|
const remappedModified = await Promise.all(delta.modified.map(async (change) => {
|
|
1989
|
+
if (!change.from || !change.to) {
|
|
1990
|
+
throw createError({
|
|
1991
|
+
code: ERROR_CODES.INTERNAL,
|
|
1992
|
+
message: "Modified diff members are missing before remap.",
|
|
1993
|
+
details: {
|
|
1994
|
+
key: change.key,
|
|
1995
|
+
kind,
|
|
1996
|
+
fromVersion,
|
|
1997
|
+
toVersion,
|
|
1998
|
+
mapping
|
|
1999
|
+
}
|
|
2000
|
+
});
|
|
2001
|
+
}
|
|
1693
2002
|
const [fromResult, toResult] = await Promise.all([
|
|
1694
2003
|
this.remapSignatureMembers([change.from], kind, fromVersion, "obfuscated", mapping, input.sourcePriority, warnings),
|
|
1695
2004
|
this.remapSignatureMembers([change.to], kind, toVersion, "obfuscated", mapping, input.sourcePriority, warnings)
|
|
1696
2005
|
]);
|
|
1697
|
-
|
|
2006
|
+
const fromMember = fromResult.members[0];
|
|
2007
|
+
const toMember = toResult.members[0];
|
|
2008
|
+
if (!fromMember || !toMember) {
|
|
2009
|
+
throw createError({
|
|
2010
|
+
code: ERROR_CODES.INTERNAL,
|
|
2011
|
+
message: "Failed to remap modified diff members.",
|
|
2012
|
+
details: {
|
|
2013
|
+
key: change.key,
|
|
2014
|
+
kind,
|
|
2015
|
+
fromVersion,
|
|
2016
|
+
toVersion,
|
|
2017
|
+
mapping
|
|
2018
|
+
}
|
|
2019
|
+
});
|
|
2020
|
+
}
|
|
2021
|
+
return { ...change, from: fromMember, to: toMember };
|
|
1698
2022
|
}));
|
|
1699
2023
|
return { added: addedResult.members, removed: removedResult.members, modified: remappedModified };
|
|
1700
2024
|
};
|
|
@@ -1737,9 +2061,9 @@ export class SourceService {
|
|
|
1737
2061
|
toVersion
|
|
1738
2062
|
},
|
|
1739
2063
|
classChange,
|
|
1740
|
-
constructors: remappedConstructors,
|
|
1741
|
-
methods: remappedMethods,
|
|
1742
|
-
fields: remappedFields,
|
|
2064
|
+
constructors: includeFullDiff ? remappedConstructors : compactDiffDelta(remappedConstructors),
|
|
2065
|
+
methods: includeFullDiff ? remappedMethods : compactDiffDelta(remappedMethods),
|
|
2066
|
+
fields: includeFullDiff ? remappedFields : compactDiffDelta(remappedFields),
|
|
1743
2067
|
summary,
|
|
1744
2068
|
warnings
|
|
1745
2069
|
};
|
|
@@ -1868,7 +2192,7 @@ export class SourceService {
|
|
|
1868
2192
|
if (normalizedArtifactId && input.target) {
|
|
1869
2193
|
throw createError({
|
|
1870
2194
|
code: ERROR_CODES.INVALID_INPUT,
|
|
1871
|
-
message: "artifactId and
|
|
2195
|
+
message: "artifactId and target are mutually exclusive.",
|
|
1872
2196
|
details: {
|
|
1873
2197
|
artifactId: normalizedArtifactId,
|
|
1874
2198
|
target: input.target
|
|
@@ -1927,6 +2251,14 @@ export class SourceService {
|
|
|
1927
2251
|
version = artifact.version;
|
|
1928
2252
|
coordinate = artifact.coordinate;
|
|
1929
2253
|
}
|
|
2254
|
+
version = await this.resolveVersionContext({
|
|
2255
|
+
version,
|
|
2256
|
+
provenance,
|
|
2257
|
+
coordinate,
|
|
2258
|
+
projectPath: input.projectPath,
|
|
2259
|
+
preferProjectVersion: input.preferProjectVersion,
|
|
2260
|
+
warnings
|
|
2261
|
+
});
|
|
1930
2262
|
let activeArtifactId = artifactId;
|
|
1931
2263
|
let activeOrigin = origin;
|
|
1932
2264
|
let activeProvenance = provenance;
|
|
@@ -1963,7 +2295,7 @@ export class SourceService {
|
|
|
1963
2295
|
activeOrigin = fallbackResolved.origin;
|
|
1964
2296
|
activeMappingApplied = fallbackResolved.mappingApplied ?? activeMappingApplied;
|
|
1965
2297
|
activeProvenance = fallbackResolved.provenance ?? activeProvenance;
|
|
1966
|
-
activeQualityFlags =
|
|
2298
|
+
activeQualityFlags = dedupeQualityFlags([...(fallbackResolved.qualityFlags ?? []), "binary-fallback"]);
|
|
1967
2299
|
activeSourceJarPath = fallbackResolved.sourceJarPath;
|
|
1968
2300
|
warnings.push(`Falling back to binary artifact "${normalizedBinaryJarPath}" because source coverage for "${className}" was incomplete.`);
|
|
1969
2301
|
if (activeMappingApplied !== requestedMapping) {
|
|
@@ -2110,8 +2442,15 @@ export class SourceService {
|
|
|
2110
2442
|
artifactId: activeArtifactId,
|
|
2111
2443
|
requestedMapping,
|
|
2112
2444
|
mappingApplied: activeMappingApplied,
|
|
2445
|
+
returnedNamespace: activeMappingApplied,
|
|
2113
2446
|
provenance: normalizedProvenance,
|
|
2114
2447
|
qualityFlags: activeQualityFlags,
|
|
2448
|
+
artifactContents: this.buildArtifactContentsSummary({
|
|
2449
|
+
origin: activeOrigin,
|
|
2450
|
+
sourceJarPath: activeSourceJarPath,
|
|
2451
|
+
isDecompiled: activeOrigin === "decompiled",
|
|
2452
|
+
qualityFlags: activeQualityFlags
|
|
2453
|
+
}),
|
|
2115
2454
|
...(resolvedOutputFile ? { outputFile: resolvedOutputFile } : {}),
|
|
2116
2455
|
warnings
|
|
2117
2456
|
};
|
|
@@ -2135,7 +2474,7 @@ export class SourceService {
|
|
|
2135
2474
|
if (normalizedArtifactId && input.target) {
|
|
2136
2475
|
throw createError({
|
|
2137
2476
|
code: ERROR_CODES.INVALID_INPUT,
|
|
2138
|
-
message: "artifactId and
|
|
2477
|
+
message: "artifactId and target are mutually exclusive.",
|
|
2139
2478
|
details: {
|
|
2140
2479
|
artifactId: normalizedArtifactId,
|
|
2141
2480
|
target: input.target
|
|
@@ -2149,6 +2488,8 @@ export class SourceService {
|
|
|
2149
2488
|
let provenance;
|
|
2150
2489
|
let qualityFlags = [];
|
|
2151
2490
|
let binaryJarPath;
|
|
2491
|
+
let sourceJarPath;
|
|
2492
|
+
let coordinate;
|
|
2152
2493
|
if (parsedMaxMembers != null && parsedMaxMembers > 5000) {
|
|
2153
2494
|
warnings.push(`maxMembers was clamped to 5000 from ${parsedMaxMembers}.`);
|
|
2154
2495
|
}
|
|
@@ -2177,7 +2518,9 @@ export class SourceService {
|
|
|
2177
2518
|
provenance = resolved.provenance;
|
|
2178
2519
|
qualityFlags = [...resolved.qualityFlags];
|
|
2179
2520
|
binaryJarPath = resolved.binaryJarPath;
|
|
2521
|
+
sourceJarPath = resolved.resolvedSourceJarPath;
|
|
2180
2522
|
version = resolved.version;
|
|
2523
|
+
coordinate = resolved.coordinate;
|
|
2181
2524
|
}
|
|
2182
2525
|
else {
|
|
2183
2526
|
const artifact = this.getArtifact(artifactId);
|
|
@@ -2186,16 +2529,29 @@ export class SourceService {
|
|
|
2186
2529
|
provenance = artifact.provenance;
|
|
2187
2530
|
qualityFlags = artifact.qualityFlags;
|
|
2188
2531
|
binaryJarPath = artifact.binaryJarPath;
|
|
2532
|
+
sourceJarPath = artifact.sourceJarPath;
|
|
2189
2533
|
version = artifact.version;
|
|
2534
|
+
coordinate = artifact.coordinate;
|
|
2190
2535
|
}
|
|
2536
|
+
version = await this.resolveVersionContext({
|
|
2537
|
+
version,
|
|
2538
|
+
provenance,
|
|
2539
|
+
coordinate,
|
|
2540
|
+
projectPath: input.projectPath,
|
|
2541
|
+
preferProjectVersion: input.preferProjectVersion,
|
|
2542
|
+
warnings
|
|
2543
|
+
});
|
|
2191
2544
|
if (requestedMapping !== "obfuscated" && !version) {
|
|
2192
2545
|
throw createError({
|
|
2193
2546
|
code: ERROR_CODES.MAPPING_NOT_APPLIED,
|
|
2194
2547
|
message: `Non-obfuscated mapping "${requestedMapping}" requires a version, but none was resolved.`,
|
|
2195
2548
|
details: {
|
|
2196
2549
|
mapping: requestedMapping,
|
|
2197
|
-
nextAction: "Resolve with
|
|
2198
|
-
suggestedCall: {
|
|
2550
|
+
nextAction: "Resolve with target: { kind: \"version\", value: ... } or specify a versioned coordinate.",
|
|
2551
|
+
suggestedCall: {
|
|
2552
|
+
tool: "resolve-artifact",
|
|
2553
|
+
params: buildResolveArtifactParams({ kind: "version", value: "latest" })
|
|
2554
|
+
}
|
|
2199
2555
|
}
|
|
2200
2556
|
});
|
|
2201
2557
|
}
|
|
@@ -2206,7 +2562,7 @@ export class SourceService {
|
|
|
2206
2562
|
details: {
|
|
2207
2563
|
artifactId,
|
|
2208
2564
|
className,
|
|
2209
|
-
nextAction: "Resolve with
|
|
2565
|
+
nextAction: "Resolve with target: { kind: \"jar\" | \"version\", value: ... } or use an artifact that has a binary jar."
|
|
2210
2566
|
}
|
|
2211
2567
|
});
|
|
2212
2568
|
}
|
|
@@ -2288,8 +2644,15 @@ export class SourceService {
|
|
|
2288
2644
|
artifactId,
|
|
2289
2645
|
requestedMapping,
|
|
2290
2646
|
mappingApplied,
|
|
2647
|
+
returnedNamespace: requestedMapping,
|
|
2291
2648
|
provenance: normalizedProvenance,
|
|
2292
2649
|
qualityFlags,
|
|
2650
|
+
artifactContents: this.buildArtifactContentsSummary({
|
|
2651
|
+
origin,
|
|
2652
|
+
sourceJarPath,
|
|
2653
|
+
isDecompiled: origin === "decompiled",
|
|
2654
|
+
qualityFlags
|
|
2655
|
+
}),
|
|
2293
2656
|
warnings
|
|
2294
2657
|
};
|
|
2295
2658
|
}
|
|
@@ -2301,7 +2664,7 @@ export class SourceService {
|
|
|
2301
2664
|
...sharedInput,
|
|
2302
2665
|
source: sourceInput.source
|
|
2303
2666
|
});
|
|
2304
|
-
return this.buildValidateMixinOutput(mode, [
|
|
2667
|
+
return this.applyValidateMixinOutputCompaction(this.buildValidateMixinOutput(mode, [
|
|
2305
2668
|
{
|
|
2306
2669
|
source: {
|
|
2307
2670
|
kind: "inline",
|
|
@@ -2309,7 +2672,7 @@ export class SourceService {
|
|
|
2309
2672
|
},
|
|
2310
2673
|
result: singleResult
|
|
2311
2674
|
}
|
|
2312
|
-
]);
|
|
2675
|
+
]), input);
|
|
2313
2676
|
}
|
|
2314
2677
|
if (mode === "path") {
|
|
2315
2678
|
const resolvedPath = this.resolveMixinInputPath(sourceInput.path, "path");
|
|
@@ -2317,7 +2680,7 @@ export class SourceService {
|
|
|
2317
2680
|
...sharedInput,
|
|
2318
2681
|
sourcePath: sourceInput.path
|
|
2319
2682
|
});
|
|
2320
|
-
return this.buildValidateMixinOutput(mode, [
|
|
2683
|
+
return this.applyValidateMixinOutputCompaction(this.buildValidateMixinOutput(mode, [
|
|
2321
2684
|
{
|
|
2322
2685
|
source: {
|
|
2323
2686
|
kind: "path",
|
|
@@ -2326,7 +2689,7 @@ export class SourceService {
|
|
|
2326
2689
|
},
|
|
2327
2690
|
result: singleResult
|
|
2328
2691
|
}
|
|
2329
|
-
]);
|
|
2692
|
+
]), input);
|
|
2330
2693
|
}
|
|
2331
2694
|
if (mode === "paths") {
|
|
2332
2695
|
return this.validateMixinMany(mode, sourceInput.paths.map((path) => ({
|
|
@@ -2338,7 +2701,10 @@ export class SourceService {
|
|
|
2338
2701
|
sourcePath: path
|
|
2339
2702
|
})), input);
|
|
2340
2703
|
}
|
|
2341
|
-
const
|
|
2704
|
+
const resolvedInput = mode === "project"
|
|
2705
|
+
? this.createProjectValidateMixinConfigInput(input)
|
|
2706
|
+
: input;
|
|
2707
|
+
const configSources = await this.resolveMixinConfigSources(resolvedInput);
|
|
2342
2708
|
if (configSources.length === 0) {
|
|
2343
2709
|
throw createError({
|
|
2344
2710
|
code: ERROR_CODES.INVALID_INPUT,
|
|
@@ -2353,10 +2719,94 @@ export class SourceService {
|
|
|
2353
2719
|
configPath: entry.configPath
|
|
2354
2720
|
},
|
|
2355
2721
|
sourcePath: entry.sourcePath
|
|
2356
|
-
})),
|
|
2722
|
+
})), resolvedInput);
|
|
2723
|
+
}
|
|
2724
|
+
createProjectValidateMixinConfigInput(input) {
|
|
2725
|
+
if (input.input.mode !== "project") {
|
|
2726
|
+
return input;
|
|
2727
|
+
}
|
|
2728
|
+
const resolvedProjectPath = this.resolveMixinInputPath(input.input.path, "path");
|
|
2729
|
+
const configPaths = fastGlob.sync(["**/*.mixins.json"], {
|
|
2730
|
+
cwd: resolvedProjectPath,
|
|
2731
|
+
absolute: true,
|
|
2732
|
+
onlyFiles: true,
|
|
2733
|
+
ignore: [...MIXIN_PROJECT_DISCOVERY_IGNORES]
|
|
2734
|
+
}).sort((left, right) => left.localeCompare(right));
|
|
2735
|
+
if (configPaths.length === 0) {
|
|
2736
|
+
throw createError({
|
|
2737
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
2738
|
+
message: `No mixin config JSON files were found under project path "${input.input.path}".`,
|
|
2739
|
+
details: {
|
|
2740
|
+
nextAction: "Use input.mode='config' with explicit configPaths[], or point input.path at the workspace root that contains *.mixins.json files."
|
|
2741
|
+
}
|
|
2742
|
+
});
|
|
2743
|
+
}
|
|
2744
|
+
return {
|
|
2745
|
+
...input,
|
|
2746
|
+
projectPath: input.projectPath ?? resolvedProjectPath,
|
|
2747
|
+
input: {
|
|
2748
|
+
mode: "config",
|
|
2749
|
+
configPaths
|
|
2750
|
+
}
|
|
2751
|
+
};
|
|
2752
|
+
}
|
|
2753
|
+
shouldRetryValidateMixinWithMavenFirst(input, result) {
|
|
2754
|
+
const initialPriority = input.retryState?.initialSourcePriority ?? input.sourcePriority ?? this.config.mappingSourcePriority;
|
|
2755
|
+
if (input.retryState?.attempted || initialPriority !== "loom-first") {
|
|
2756
|
+
return false;
|
|
2757
|
+
}
|
|
2758
|
+
if (result.validationStatus !== "partial") {
|
|
2759
|
+
return false;
|
|
2760
|
+
}
|
|
2761
|
+
if (result.summary.membersSkipped > 0) {
|
|
2762
|
+
return true;
|
|
2763
|
+
}
|
|
2764
|
+
return result.issues.some((issue) => issue.resolutionPath === "source-signature-unavailable" ||
|
|
2765
|
+
issue.resolutionPath === "target-mapping-failed" ||
|
|
2766
|
+
issue.resolutionPath === "member-remap-failed");
|
|
2767
|
+
}
|
|
2768
|
+
findValidateMixinClassMapping(input) {
|
|
2769
|
+
const cache = input.batchCaches?.classMappings;
|
|
2770
|
+
if (!cache) {
|
|
2771
|
+
return this.mappingService.findMapping({
|
|
2772
|
+
version: input.version,
|
|
2773
|
+
kind: "class",
|
|
2774
|
+
name: input.className,
|
|
2775
|
+
sourceMapping: input.sourceMapping,
|
|
2776
|
+
targetMapping: input.targetMapping,
|
|
2777
|
+
sourcePriority: input.sourcePriority
|
|
2778
|
+
});
|
|
2779
|
+
}
|
|
2780
|
+
const cacheKey = [
|
|
2781
|
+
input.version,
|
|
2782
|
+
input.className,
|
|
2783
|
+
input.sourceMapping,
|
|
2784
|
+
input.targetMapping,
|
|
2785
|
+
input.sourcePriority
|
|
2786
|
+
].join("\0");
|
|
2787
|
+
const cached = cache.get(cacheKey);
|
|
2788
|
+
if (cached) {
|
|
2789
|
+
return cached;
|
|
2790
|
+
}
|
|
2791
|
+
const pending = this.mappingService.findMapping({
|
|
2792
|
+
version: input.version,
|
|
2793
|
+
kind: "class",
|
|
2794
|
+
name: input.className,
|
|
2795
|
+
sourceMapping: input.sourceMapping,
|
|
2796
|
+
targetMapping: input.targetMapping,
|
|
2797
|
+
sourcePriority: input.sourcePriority
|
|
2798
|
+
}).catch((error) => {
|
|
2799
|
+
cache.delete(cacheKey);
|
|
2800
|
+
throw error;
|
|
2801
|
+
});
|
|
2802
|
+
cache.set(cacheKey, pending);
|
|
2803
|
+
return pending;
|
|
2357
2804
|
}
|
|
2358
2805
|
async validateMixinSingle(input) {
|
|
2359
2806
|
let version = input.version.trim();
|
|
2807
|
+
const requestedScope = normalizeRequestedArtifactScope(input.scope);
|
|
2808
|
+
const currentSourcePriority = input.sourcePriority ?? this.config.mappingSourcePriority;
|
|
2809
|
+
const initialSourcePriority = input.retryState?.initialSourcePriority ?? currentSourcePriority;
|
|
2360
2810
|
if (!version) {
|
|
2361
2811
|
throw createError({ code: ERROR_CODES.INVALID_INPUT, message: "version must be non-empty." });
|
|
2362
2812
|
}
|
|
@@ -2417,22 +2867,25 @@ export class SourceService {
|
|
|
2417
2867
|
}
|
|
2418
2868
|
// Resolve jar: use Loom cache for non-vanilla scope with projectPath
|
|
2419
2869
|
let jarPath;
|
|
2870
|
+
let resolvedArtifact;
|
|
2871
|
+
let signatureLookupMapping = "obfuscated";
|
|
2420
2872
|
let scopeFallback;
|
|
2421
2873
|
if (input.scope && input.scope !== "vanilla" && input.projectPath) {
|
|
2422
2874
|
try {
|
|
2423
|
-
|
|
2875
|
+
resolvedArtifact = await this.resolveArtifact({
|
|
2424
2876
|
target: { kind: "version", value: version },
|
|
2425
2877
|
mapping: requestedMapping,
|
|
2426
|
-
sourcePriority:
|
|
2878
|
+
sourcePriority: currentSourcePriority,
|
|
2427
2879
|
projectPath: input.projectPath,
|
|
2428
2880
|
scope: input.scope,
|
|
2429
2881
|
preferProjectVersion: false
|
|
2430
2882
|
});
|
|
2431
|
-
jarPath =
|
|
2432
|
-
warnings.push(...
|
|
2433
|
-
mappingApplied =
|
|
2434
|
-
|
|
2435
|
-
|
|
2883
|
+
jarPath = resolvedArtifact.binaryJarPath ?? (await this.versionService.resolveVersionJar(version)).jarPath;
|
|
2884
|
+
warnings.push(...resolvedArtifact.warnings);
|
|
2885
|
+
mappingApplied = resolvedArtifact.mappingApplied;
|
|
2886
|
+
signatureLookupMapping = resolvedArtifact.mappingApplied;
|
|
2887
|
+
if (resolvedArtifact.version) {
|
|
2888
|
+
version = resolvedArtifact.version;
|
|
2436
2889
|
}
|
|
2437
2890
|
}
|
|
2438
2891
|
catch (scopeErr) {
|
|
@@ -2453,6 +2906,7 @@ export class SourceService {
|
|
|
2453
2906
|
if (jarPath.includes("-sources.jar")) {
|
|
2454
2907
|
warnings.push(`Resolved jar appears to be a sources jar. Falling back to vanilla client jar.`);
|
|
2455
2908
|
jarPath = (await this.versionService.resolveVersionJar(version)).jarPath;
|
|
2909
|
+
signatureLookupMapping = "obfuscated";
|
|
2456
2910
|
scopeFallback = {
|
|
2457
2911
|
requested: input.scope ?? "vanilla",
|
|
2458
2912
|
applied: "vanilla",
|
|
@@ -2465,7 +2919,7 @@ export class SourceService {
|
|
|
2465
2919
|
const health = await this.mappingService.checkMappingHealth({
|
|
2466
2920
|
version,
|
|
2467
2921
|
requestedMapping,
|
|
2468
|
-
sourcePriority:
|
|
2922
|
+
sourcePriority: currentSourcePriority
|
|
2469
2923
|
});
|
|
2470
2924
|
const jarAvailable = existsSync(jarPath);
|
|
2471
2925
|
healthReport = {
|
|
@@ -2513,28 +2967,28 @@ export class SourceService {
|
|
|
2513
2967
|
}
|
|
2514
2968
|
}
|
|
2515
2969
|
let obfuscatedName = resolvedClassName;
|
|
2516
|
-
if (requestedMapping !==
|
|
2970
|
+
if (requestedMapping !== signatureLookupMapping) {
|
|
2517
2971
|
try {
|
|
2518
|
-
const mapped = await this.
|
|
2972
|
+
const mapped = await this.findValidateMixinClassMapping({
|
|
2519
2973
|
version,
|
|
2520
|
-
|
|
2521
|
-
name: resolvedClassName,
|
|
2974
|
+
className: resolvedClassName,
|
|
2522
2975
|
sourceMapping: requestedMapping,
|
|
2523
|
-
targetMapping:
|
|
2524
|
-
sourcePriority:
|
|
2976
|
+
targetMapping: signatureLookupMapping,
|
|
2977
|
+
sourcePriority: currentSourcePriority,
|
|
2978
|
+
batchCaches: input.batchCaches
|
|
2525
2979
|
});
|
|
2526
2980
|
if (mapped.resolved && mapped.resolvedSymbol) {
|
|
2527
2981
|
obfuscatedName = mapped.resolvedSymbol.name;
|
|
2528
2982
|
resolutionTrace?.push({ target: target.className, step: "mapping", input: resolvedClassName, output: obfuscatedName, success: true });
|
|
2529
2983
|
}
|
|
2530
2984
|
else {
|
|
2531
|
-
warnings.push(`Could not map class "${resolvedClassName}" from ${requestedMapping} to
|
|
2985
|
+
warnings.push(`Could not map class "${resolvedClassName}" from ${requestedMapping} to ${signatureLookupMapping}; using "${obfuscatedName}" for lookup.`);
|
|
2532
2986
|
mappingFailedTargets.add(target.className);
|
|
2533
2987
|
resolutionTrace?.push({ target: target.className, step: "mapping", input: resolvedClassName, output: obfuscatedName, success: false, detail: "No mapping found" });
|
|
2534
2988
|
}
|
|
2535
2989
|
}
|
|
2536
2990
|
catch (mapErr) {
|
|
2537
|
-
warnings.push(`Mapping lookup failed for class "${resolvedClassName}"; using "${obfuscatedName}" for lookup.`);
|
|
2991
|
+
warnings.push(`Mapping lookup failed for class "${resolvedClassName}" while preparing ${signatureLookupMapping} lookup; using "${obfuscatedName}" for lookup.`);
|
|
2538
2992
|
mappingFailedTargets.add(target.className);
|
|
2539
2993
|
resolutionTrace?.push({ target: target.className, step: "mapping", input: resolvedClassName, output: obfuscatedName, success: false, detail: mapErr instanceof Error ? mapErr.message : String(mapErr) });
|
|
2540
2994
|
}
|
|
@@ -2551,12 +3005,12 @@ export class SourceService {
|
|
|
2551
3005
|
let constructors = sig.constructors;
|
|
2552
3006
|
let methods = sig.methods;
|
|
2553
3007
|
let fields = sig.fields;
|
|
2554
|
-
if (requestedMapping !==
|
|
3008
|
+
if (requestedMapping !== signatureLookupMapping) {
|
|
2555
3009
|
try {
|
|
2556
3010
|
const [ctorResult, methodResult, fieldResult] = await Promise.all([
|
|
2557
|
-
this.remapSignatureMembers(sig.constructors, "method", version,
|
|
2558
|
-
this.remapSignatureMembers(sig.methods, "method", version,
|
|
2559
|
-
this.remapSignatureMembers(sig.fields, "field", version,
|
|
3011
|
+
this.remapSignatureMembers(sig.constructors, "method", version, signatureLookupMapping, requestedMapping, currentSourcePriority, warnings),
|
|
3012
|
+
this.remapSignatureMembers(sig.methods, "method", version, signatureLookupMapping, requestedMapping, currentSourcePriority, warnings),
|
|
3013
|
+
this.remapSignatureMembers(sig.fields, "field", version, signatureLookupMapping, requestedMapping, currentSourcePriority, warnings)
|
|
2560
3014
|
]);
|
|
2561
3015
|
constructors = ctorResult.members;
|
|
2562
3016
|
methods = methodResult.members;
|
|
@@ -2578,9 +3032,17 @@ export class SourceService {
|
|
|
2578
3032
|
}
|
|
2579
3033
|
}
|
|
2580
3034
|
catch (remapErr) {
|
|
2581
|
-
warnings.push(`Member remapping failed for "${resolvedClassName}"; falling back to
|
|
2582
|
-
|
|
2583
|
-
|
|
3035
|
+
warnings.push(`Member remapping failed for "${resolvedClassName}"; falling back to ${signatureLookupMapping} names. ` +
|
|
3036
|
+
`Member names shown may be in the ${signatureLookupMapping} runtime namespace.`);
|
|
3037
|
+
mappingApplied = signatureLookupMapping;
|
|
3038
|
+
resolutionTrace?.push({
|
|
3039
|
+
target: target.className,
|
|
3040
|
+
step: "remap",
|
|
3041
|
+
input: resolvedClassName,
|
|
3042
|
+
output: `${signatureLookupMapping} fallback`,
|
|
3043
|
+
success: false,
|
|
3044
|
+
detail: remapErr instanceof Error ? remapErr.message : String(remapErr)
|
|
3045
|
+
});
|
|
2584
3046
|
}
|
|
2585
3047
|
}
|
|
2586
3048
|
targetMembers.set(target.className, {
|
|
@@ -2592,16 +3054,14 @@ export class SourceService {
|
|
|
2592
3054
|
}
|
|
2593
3055
|
catch (sigErr) {
|
|
2594
3056
|
warnings.push(`Could not load signature for class "${resolvedClassName}" (obfuscated: "${obfuscatedName}").`);
|
|
2595
|
-
signatureFailedTargets.add(target.className);
|
|
2596
3057
|
resolutionTrace?.push({ target: target.className, step: "signature", input: obfuscatedName, output: "CLASS_NOT_FOUND", success: false, detail: sigErr instanceof Error ? sigErr.message : String(sigErr) });
|
|
2597
3058
|
// Fallback: check if the symbol exists in the mapping graph even though getSignature failed
|
|
2598
3059
|
try {
|
|
2599
3060
|
const existenceCheck = await this.mappingService.checkSymbolExists({
|
|
2600
3061
|
version, kind: "class", name: resolvedClassName,
|
|
2601
|
-
sourceMapping: requestedMapping, nameMode: "auto"
|
|
3062
|
+
sourceMapping: requestedMapping, nameMode: "auto", sourcePriority: currentSourcePriority
|
|
2602
3063
|
});
|
|
2603
3064
|
if (existenceCheck.resolved) {
|
|
2604
|
-
signatureFailedTargets.delete(target.className);
|
|
2605
3065
|
symbolExistsButSignatureFailed.add(target.className);
|
|
2606
3066
|
resolutionTrace?.push({ target: target.className, step: "fallback-check", input: resolvedClassName, output: "exists in mapping graph", success: true });
|
|
2607
3067
|
}
|
|
@@ -2610,23 +3070,35 @@ export class SourceService {
|
|
|
2610
3070
|
}
|
|
2611
3071
|
}
|
|
2612
3072
|
catch {
|
|
2613
|
-
// Fallback check failed —
|
|
3073
|
+
// Fallback check failed — treat as tool-limited partial validation.
|
|
3074
|
+
signatureFailedTargets.add(target.className);
|
|
2614
3075
|
resolutionTrace?.push({ target: target.className, step: "fallback-check", input: resolvedClassName, output: "check failed", success: false });
|
|
2615
3076
|
}
|
|
2616
3077
|
}
|
|
2617
3078
|
}
|
|
2618
3079
|
// Fix toolHealth accuracy: reflect actual failures after target resolution
|
|
2619
3080
|
if (healthReport) {
|
|
2620
|
-
const hasFailures = signatureFailedTargets.size > 0 ||
|
|
3081
|
+
const hasFailures = signatureFailedTargets.size > 0 ||
|
|
3082
|
+
mappingFailedTargets.size > 0 ||
|
|
3083
|
+
symbolExistsButSignatureFailed.size > 0;
|
|
2621
3084
|
if (hasFailures && healthReport.overallHealthy) {
|
|
2622
3085
|
healthReport.overallHealthy = false;
|
|
2623
|
-
healthReport.degradations.push(`${mappingFailedTargets.size} mapping failure(s), ${signatureFailedTargets.size} signature failure(s).`);
|
|
3086
|
+
healthReport.degradations.push(`${mappingFailedTargets.size} mapping failure(s), ${signatureFailedTargets.size} signature failure(s), ${symbolExistsButSignatureFailed.size} partial validation target(s).`);
|
|
2624
3087
|
}
|
|
2625
3088
|
}
|
|
2626
3089
|
const resolutionNotes = [];
|
|
2627
3090
|
if (requestedMapping !== mappingApplied) {
|
|
2628
3091
|
resolutionNotes.push(`Mapping fallback: requested "${requestedMapping}" but applied "${mappingApplied}" due to remapping failure.`);
|
|
2629
3092
|
}
|
|
3093
|
+
const appliedScope = inferAppliedArtifactScope({
|
|
3094
|
+
requestedScope,
|
|
3095
|
+
scopeFallback,
|
|
3096
|
+
jarPath,
|
|
3097
|
+
resolvedSourceJarPath: resolvedArtifact?.resolvedSourceJarPath
|
|
3098
|
+
});
|
|
3099
|
+
if (!scopeFallback && requestedScope !== appliedScope) {
|
|
3100
|
+
resolutionNotes.push(`Scope adjusted during validation: requested "${requestedScope}" but resolved artifact looks like "${appliedScope}".`);
|
|
3101
|
+
}
|
|
2630
3102
|
// Count remap failures from warnings
|
|
2631
3103
|
const REMAP_WARNING_RE = /^(?:Could not remap|Remap failed for)\b/;
|
|
2632
3104
|
const remapFailures = warnings.filter((w) => REMAP_WARNING_RE.test(w)).length;
|
|
@@ -2640,26 +3112,30 @@ export class SourceService {
|
|
|
2640
3112
|
}
|
|
2641
3113
|
// Build mapping chain description
|
|
2642
3114
|
const mappingChain = [];
|
|
2643
|
-
if (requestedMapping !==
|
|
2644
|
-
mappingChain.push(`${requestedMapping} →
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
}
|
|
3115
|
+
if (requestedMapping !== signatureLookupMapping) {
|
|
3116
|
+
mappingChain.push(`${requestedMapping} → ${signatureLookupMapping}`);
|
|
3117
|
+
}
|
|
3118
|
+
if (mappingApplied !== signatureLookupMapping) {
|
|
3119
|
+
mappingChain.push(`fallback to ${mappingApplied}`);
|
|
2648
3120
|
}
|
|
2649
3121
|
const provenance = {
|
|
2650
3122
|
version,
|
|
2651
3123
|
jarPath,
|
|
2652
3124
|
requestedMapping,
|
|
2653
3125
|
mappingApplied,
|
|
3126
|
+
requestedScope,
|
|
3127
|
+
appliedScope,
|
|
3128
|
+
requestedSourcePriority: initialSourcePriority,
|
|
3129
|
+
appliedSourcePriority: currentSourcePriority,
|
|
2654
3130
|
resolutionNotes: resolutionNotes.length > 0 ? resolutionNotes : undefined,
|
|
2655
|
-
jarType:
|
|
3131
|
+
jarType: scopeToJarType(appliedScope),
|
|
2656
3132
|
mappingChain: mappingChain.length > 0 ? mappingChain : undefined,
|
|
2657
3133
|
remapFailures: remapFailures > 0 ? remapFailures : undefined,
|
|
2658
3134
|
mappingAutoDetected: mappingAutoDetected || undefined,
|
|
2659
3135
|
scopeFallback,
|
|
2660
3136
|
resolutionTrace: resolutionTrace && resolutionTrace.length > 0 ? resolutionTrace : undefined
|
|
2661
3137
|
};
|
|
2662
|
-
const result = validateParsedMixin(parsed, targetMembers, warnings, provenance, confidence, mappingFailedTargets, input.explain, remapFailedMembers, signatureFailedTargets, input.explain ? { scope:
|
|
3138
|
+
const result = refreshMixinValidationOutcome(validateParsedMixin(parsed, targetMembers, warnings, provenance, confidence, mappingFailedTargets, input.explain, remapFailedMembers, signatureFailedTargets, input.explain ? { scope: requestedScope, sourcePriority: currentSourcePriority, projectPath: input.projectPath, mapping: requestedMapping } : undefined, input.warningMode, healthReport, symbolExistsButSignatureFailed.size > 0 ? symbolExistsButSignatureFailed : undefined));
|
|
2663
3139
|
// Apply minSeverity / hideUncertain filters
|
|
2664
3140
|
const minSeverity = input.minSeverity ?? "all";
|
|
2665
3141
|
const hideUncertain = input.hideUncertain ?? false;
|
|
@@ -2692,7 +3168,6 @@ export class SourceService {
|
|
|
2692
3168
|
parseWarnings: filteredParseWarnings
|
|
2693
3169
|
};
|
|
2694
3170
|
result.unfilteredSummary = unfilteredSummary;
|
|
2695
|
-
result.valid = filteredDefiniteErrors === 0;
|
|
2696
3171
|
}
|
|
2697
3172
|
// Apply warningCategoryFilter
|
|
2698
3173
|
if (input.warningCategoryFilter && input.warningCategoryFilter.length > 0) {
|
|
@@ -2716,7 +3191,6 @@ export class SourceService {
|
|
|
2716
3191
|
resolutionErrors: result.issues.filter((i) => i.resolutionPath != null).length,
|
|
2717
3192
|
parseWarnings: result.issues.filter((i) => i.category === "parse").length
|
|
2718
3193
|
};
|
|
2719
|
-
result.valid = catDefiniteErrors === 0;
|
|
2720
3194
|
}
|
|
2721
3195
|
// Apply treatInfoAsWarning filter
|
|
2722
3196
|
if (input.treatInfoAsWarning === false && result.structuredWarnings) {
|
|
@@ -2730,10 +3204,41 @@ export class SourceService {
|
|
|
2730
3204
|
result.structuredWarnings = undefined;
|
|
2731
3205
|
result.aggregatedWarnings = undefined;
|
|
2732
3206
|
result.toolHealth = undefined;
|
|
3207
|
+
result.confidenceBreakdown = undefined;
|
|
2733
3208
|
if (result.provenance) {
|
|
2734
3209
|
result.provenance.resolutionTrace = undefined;
|
|
2735
3210
|
}
|
|
2736
3211
|
}
|
|
3212
|
+
refreshMixinValidationOutcome(result);
|
|
3213
|
+
if (this.shouldRetryValidateMixinWithMavenFirst(input, result)) {
|
|
3214
|
+
const retryWarning = `Retrying validate-mixin with sourcePriority="maven-first" after partial validation using "${currentSourcePriority}".`;
|
|
3215
|
+
try {
|
|
3216
|
+
const retried = await this.validateMixinSingle({
|
|
3217
|
+
...input,
|
|
3218
|
+
source,
|
|
3219
|
+
sourcePath: undefined,
|
|
3220
|
+
sourcePriority: "maven-first",
|
|
3221
|
+
retryState: {
|
|
3222
|
+
attempted: true,
|
|
3223
|
+
initialSourcePriority
|
|
3224
|
+
}
|
|
3225
|
+
});
|
|
3226
|
+
retried.warnings = [retryWarning, ...retried.warnings];
|
|
3227
|
+
if (retried.provenance) {
|
|
3228
|
+
retried.provenance.requestedSourcePriority = initialSourcePriority;
|
|
3229
|
+
retried.provenance.appliedSourcePriority = "maven-first";
|
|
3230
|
+
retried.provenance.resolutionNotes = [
|
|
3231
|
+
...(retried.provenance.resolutionNotes ?? []),
|
|
3232
|
+
`Validation retried with sourcePriority "maven-first" after partial result from "${currentSourcePriority}".`
|
|
3233
|
+
];
|
|
3234
|
+
}
|
|
3235
|
+
return refreshMixinValidationOutcome(retried);
|
|
3236
|
+
}
|
|
3237
|
+
catch (retryErr) {
|
|
3238
|
+
result.warnings.unshift(`${retryWarning} Retry failed: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`);
|
|
3239
|
+
return result;
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
2737
3242
|
return result;
|
|
2738
3243
|
}
|
|
2739
3244
|
resolveMixinInputPath(rawPath, fieldName) {
|
|
@@ -2807,12 +3312,16 @@ export class SourceService {
|
|
|
2807
3312
|
const results = [];
|
|
2808
3313
|
const batchWarningMode = input.warningMode ?? "aggregated";
|
|
2809
3314
|
const { input: _discardedInput, ...sharedInput } = input;
|
|
3315
|
+
const batchCaches = {
|
|
3316
|
+
classMappings: new Map()
|
|
3317
|
+
};
|
|
2810
3318
|
for (const entry of entries) {
|
|
2811
3319
|
try {
|
|
2812
3320
|
const singleResult = await this.validateMixinSingle({
|
|
2813
3321
|
...sharedInput,
|
|
2814
3322
|
sourcePath: entry.sourcePath,
|
|
2815
|
-
warningMode: batchWarningMode
|
|
3323
|
+
warningMode: batchWarningMode,
|
|
3324
|
+
batchCaches
|
|
2816
3325
|
});
|
|
2817
3326
|
results.push({
|
|
2818
3327
|
source: entry.source,
|
|
@@ -2826,15 +3335,62 @@ export class SourceService {
|
|
|
2826
3335
|
});
|
|
2827
3336
|
}
|
|
2828
3337
|
}
|
|
2829
|
-
return this.buildValidateMixinOutput(mode, results);
|
|
3338
|
+
return this.applyValidateMixinOutputCompaction(this.buildValidateMixinOutput(mode, results), input);
|
|
3339
|
+
}
|
|
3340
|
+
applyValidateMixinOutputCompaction(output, input) {
|
|
3341
|
+
let nextOutput = output;
|
|
3342
|
+
const canHoistProvenance = nextOutput.provenance != null;
|
|
3343
|
+
const warningCandidates = nextOutput.results
|
|
3344
|
+
.map((entry) => entry.result?.warnings)
|
|
3345
|
+
.filter((entry) => entry != null);
|
|
3346
|
+
const canHoistWarnings = warningCandidates.length === 0
|
|
3347
|
+
? true
|
|
3348
|
+
: warningCandidates.every((entry) => sameStringArray(entry, warningCandidates[0]));
|
|
3349
|
+
if (input.reportMode === "summary-first") {
|
|
3350
|
+
nextOutput = {
|
|
3351
|
+
...nextOutput,
|
|
3352
|
+
results: nextOutput.results.map((entry) => (entry.result
|
|
3353
|
+
? {
|
|
3354
|
+
...entry,
|
|
3355
|
+
result: {
|
|
3356
|
+
...entry.result,
|
|
3357
|
+
warnings: canHoistWarnings ? [] : entry.result.warnings,
|
|
3358
|
+
structuredWarnings: undefined,
|
|
3359
|
+
aggregatedWarnings: undefined,
|
|
3360
|
+
resolvedMembers: undefined,
|
|
3361
|
+
toolHealth: undefined,
|
|
3362
|
+
confidenceBreakdown: undefined,
|
|
3363
|
+
provenance: canHoistProvenance ? undefined : entry.result.provenance
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
: entry))
|
|
3367
|
+
};
|
|
3368
|
+
}
|
|
3369
|
+
if (input.includeIssues !== false) {
|
|
3370
|
+
return nextOutput;
|
|
3371
|
+
}
|
|
3372
|
+
return {
|
|
3373
|
+
...nextOutput,
|
|
3374
|
+
results: nextOutput.results.map((entry) => (entry.result
|
|
3375
|
+
? {
|
|
3376
|
+
...entry,
|
|
3377
|
+
result: {
|
|
3378
|
+
...entry.result,
|
|
3379
|
+
issues: []
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
: entry))
|
|
3383
|
+
};
|
|
2830
3384
|
}
|
|
2831
3385
|
buildValidateMixinOutput(mode, results) {
|
|
2832
3386
|
let valid = 0;
|
|
3387
|
+
let partial = 0;
|
|
2833
3388
|
let invalid = 0;
|
|
2834
3389
|
let processingErrors = 0;
|
|
2835
3390
|
let totalValidationErrors = 0;
|
|
2836
3391
|
let totalValidationWarnings = 0;
|
|
2837
3392
|
const warningSet = new Set();
|
|
3393
|
+
const incompleteReasonSet = new Set();
|
|
2838
3394
|
const issueGroupMap = new Map();
|
|
2839
3395
|
for (const entry of results) {
|
|
2840
3396
|
if (!entry.result) {
|
|
@@ -2847,12 +3403,18 @@ export class SourceService {
|
|
|
2847
3403
|
else {
|
|
2848
3404
|
invalid++;
|
|
2849
3405
|
}
|
|
3406
|
+
if (entry.result.validationStatus === "partial") {
|
|
3407
|
+
partial++;
|
|
3408
|
+
}
|
|
2850
3409
|
totalValidationErrors += entry.result.summary.errors;
|
|
2851
3410
|
totalValidationWarnings += entry.result.summary.warnings;
|
|
2852
3411
|
for (const warning of entry.result.warnings) {
|
|
2853
3412
|
warningSet.add(warning);
|
|
2854
3413
|
}
|
|
2855
3414
|
for (const issue of entry.result.issues) {
|
|
3415
|
+
if (issue.kind === "validation-incomplete") {
|
|
3416
|
+
incompleteReasonSet.add(`validation-incomplete: ${issue.message}`);
|
|
3417
|
+
}
|
|
2856
3418
|
const key = `${issue.kind}\0${issue.confidence ?? "unknown"}\0${issue.category ?? "validation"}`;
|
|
2857
3419
|
const existing = issueGroupMap.get(key);
|
|
2858
3420
|
if (existing) {
|
|
@@ -2873,6 +3435,14 @@ export class SourceService {
|
|
|
2873
3435
|
}
|
|
2874
3436
|
}
|
|
2875
3437
|
const issueSummary = issueGroupMap.size > 0 ? [...issueGroupMap.values()] : undefined;
|
|
3438
|
+
const provenanceCandidates = results
|
|
3439
|
+
.map((entry) => entry.result?.provenance)
|
|
3440
|
+
.filter((entry) => entry != null);
|
|
3441
|
+
const provenance = provenanceCandidates.length === 0
|
|
3442
|
+
? undefined
|
|
3443
|
+
: provenanceCandidates.every((entry) => sameMixinValidationProvenance(entry, provenanceCandidates[0]))
|
|
3444
|
+
? provenanceCandidates[0]
|
|
3445
|
+
: undefined;
|
|
2876
3446
|
const toolHealth = results.find((entry) => entry.result?.toolHealth)?.result?.toolHealth;
|
|
2877
3447
|
const confidenceScores = results
|
|
2878
3448
|
.map((entry) => entry.result?.confidenceScore)
|
|
@@ -2883,12 +3453,15 @@ export class SourceService {
|
|
|
2883
3453
|
summary: {
|
|
2884
3454
|
total: results.length,
|
|
2885
3455
|
valid,
|
|
3456
|
+
partial,
|
|
2886
3457
|
invalid,
|
|
2887
3458
|
processingErrors,
|
|
2888
3459
|
totalValidationErrors,
|
|
2889
3460
|
totalValidationWarnings
|
|
2890
3461
|
},
|
|
2891
3462
|
issueSummary,
|
|
3463
|
+
provenance,
|
|
3464
|
+
incompleteReasons: incompleteReasonSet.size > 0 ? [...incompleteReasonSet] : undefined,
|
|
2892
3465
|
toolHealth,
|
|
2893
3466
|
confidenceScore: confidenceScores.length > 0 ? Math.min(...confidenceScores) : undefined,
|
|
2894
3467
|
warnings: [...warningSet]
|
|
@@ -3208,15 +3781,13 @@ export class SourceService {
|
|
|
3208
3781
|
this.metrics.recordSearchDbRoundtrip();
|
|
3209
3782
|
this.metrics.recordSearchRowsScanned(candidates.length);
|
|
3210
3783
|
const result = [];
|
|
3784
|
+
const glob = scope?.fileGlob ? buildGlobRegex(normalizePathStyle(scope.fileGlob)) : undefined;
|
|
3211
3785
|
for (const symbol of candidates) {
|
|
3212
3786
|
if (!checkPackagePrefix(symbol.filePath, scope?.packagePrefix)) {
|
|
3213
3787
|
continue;
|
|
3214
3788
|
}
|
|
3215
|
-
if (
|
|
3216
|
-
|
|
3217
|
-
if (!glob.test(symbol.filePath)) {
|
|
3218
|
-
continue;
|
|
3219
|
-
}
|
|
3789
|
+
if (glob && !glob.test(symbol.filePath)) {
|
|
3790
|
+
continue;
|
|
3220
3791
|
}
|
|
3221
3792
|
if (!isSymbolKind(symbol.symbolKind)) {
|
|
3222
3793
|
continue;
|
|
@@ -3310,25 +3881,13 @@ export class SourceService {
|
|
|
3310
3881
|
if (innerIndex > 0) {
|
|
3311
3882
|
candidates.add(`${classPath.slice(0, innerIndex)}.java`);
|
|
3312
3883
|
}
|
|
3313
|
-
for (const candidate of candidates) {
|
|
3314
|
-
const row = this.filesRepo.getFileContent(artifactId, candidate);
|
|
3315
|
-
if (row) {
|
|
3316
|
-
return row.filePath;
|
|
3317
|
-
}
|
|
3318
|
-
}
|
|
3319
3884
|
const simpleName = normalizedClassName.split(/[.$]/).at(-1);
|
|
3320
3885
|
if (!simpleName) {
|
|
3321
3886
|
return undefined;
|
|
3322
3887
|
}
|
|
3323
|
-
const
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
}
|
|
3327
|
-
const byName = this.filesRepo.findFirstFilePathByName(artifactId, `${simpleName}.java`);
|
|
3328
|
-
if (byName && isPackageCompatible(byName, classPath)) {
|
|
3329
|
-
return byName;
|
|
3330
|
-
}
|
|
3331
|
-
return undefined;
|
|
3888
|
+
const lastSlash = classPath.lastIndexOf("/");
|
|
3889
|
+
const expectedPrefix = lastSlash < 0 ? "" : classPath.slice(0, lastSlash + 1);
|
|
3890
|
+
return this.filesRepo.findBestClassLookupPath(artifactId, [...candidates], normalizedClassName, simpleName, expectedPrefix);
|
|
3332
3891
|
}
|
|
3333
3892
|
async resolveBinaryFallbackArtifact(input) {
|
|
3334
3893
|
const binaryJarPath = normalizeOptionalString(input.binaryJarPath);
|
|
@@ -3342,9 +3901,11 @@ export class SourceService {
|
|
|
3342
3901
|
fallbackResolved.requestedMapping = input.requestedMapping;
|
|
3343
3902
|
fallbackResolved.mappingApplied = input.mappingApplied;
|
|
3344
3903
|
fallbackResolved.provenance = input.provenance;
|
|
3345
|
-
fallbackResolved.qualityFlags = [
|
|
3346
|
-
...
|
|
3347
|
-
|
|
3904
|
+
fallbackResolved.qualityFlags = dedupeQualityFlags([
|
|
3905
|
+
...(fallbackResolved.qualityFlags ?? []),
|
|
3906
|
+
...input.qualityFlags,
|
|
3907
|
+
"binary-fallback"
|
|
3908
|
+
]);
|
|
3348
3909
|
await this.ingestIfNeeded(fallbackResolved);
|
|
3349
3910
|
return fallbackResolved;
|
|
3350
3911
|
}
|
|
@@ -3509,16 +4070,32 @@ export class SourceService {
|
|
|
3509
4070
|
};
|
|
3510
4071
|
}
|
|
3511
4072
|
try {
|
|
3512
|
-
const
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
4073
|
+
const canResolveMethodExactly = kind === "method" &&
|
|
4074
|
+
descriptor &&
|
|
4075
|
+
"resolveMethodMappingExact" in this.mappingService &&
|
|
4076
|
+
typeof this.mappingService.resolveMethodMappingExact === "function";
|
|
4077
|
+
const mapped = canResolveMethodExactly
|
|
4078
|
+
? await this.mappingService.resolveMethodMappingExact({
|
|
4079
|
+
version,
|
|
4080
|
+
owner: ownerInSourceMapping,
|
|
4081
|
+
name,
|
|
4082
|
+
descriptor,
|
|
4083
|
+
sourceMapping: mapping,
|
|
4084
|
+
targetMapping: "obfuscated",
|
|
4085
|
+
sourcePriority
|
|
4086
|
+
})
|
|
4087
|
+
: await this.mappingService.findMapping({
|
|
4088
|
+
version,
|
|
4089
|
+
kind,
|
|
4090
|
+
name,
|
|
4091
|
+
owner: ownerInSourceMapping,
|
|
4092
|
+
descriptor,
|
|
4093
|
+
signatureMode: kind === "method" && !descriptor ? "name-only" : undefined,
|
|
4094
|
+
sourceMapping: mapping,
|
|
4095
|
+
targetMapping: "obfuscated",
|
|
4096
|
+
sourcePriority
|
|
4097
|
+
});
|
|
4098
|
+
warnings.push(...mapped.warnings);
|
|
3522
4099
|
if (mapped.resolved && mapped.resolvedSymbol) {
|
|
3523
4100
|
return {
|
|
3524
4101
|
name: mapped.resolvedSymbol.name,
|
|
@@ -3527,8 +4104,8 @@ export class SourceService {
|
|
|
3527
4104
|
}
|
|
3528
4105
|
warnings.push(`Could not map ${kind} "${name}" from ${mapping} to obfuscated.`);
|
|
3529
4106
|
}
|
|
3530
|
-
catch {
|
|
3531
|
-
warnings.push(`Mapping lookup failed for ${kind} "${name}"
|
|
4107
|
+
catch (caughtError) {
|
|
4108
|
+
warnings.push(`Mapping lookup failed for ${kind} "${name}": ${caughtError instanceof Error ? caughtError.message : String(caughtError)}`);
|
|
3532
4109
|
}
|
|
3533
4110
|
return {
|
|
3534
4111
|
name,
|
|
@@ -3707,6 +4284,7 @@ export class SourceService {
|
|
|
3707
4284
|
});
|
|
3708
4285
|
});
|
|
3709
4286
|
tx();
|
|
4287
|
+
this.upsertCacheMetrics(resolved.artifactId, rebuilt.totalContentBytes, timestamp);
|
|
3710
4288
|
log("info", "index.rebuild.done", {
|
|
3711
4289
|
artifactId: resolved.artifactId,
|
|
3712
4290
|
reason,
|
|
@@ -3787,7 +4365,8 @@ export class SourceService {
|
|
|
3787
4365
|
files,
|
|
3788
4366
|
symbols,
|
|
3789
4367
|
indexedAt: new Date().toISOString(),
|
|
3790
|
-
indexDurationMs: Date.now() - indexStartedAt
|
|
4368
|
+
indexDurationMs: Date.now() - indexStartedAt,
|
|
4369
|
+
totalContentBytes: files.reduce((sum, file) => sum + file.contentBytes, 0)
|
|
3791
4370
|
};
|
|
3792
4371
|
}
|
|
3793
4372
|
getArtifact(artifactId) {
|
|
@@ -3807,7 +4386,10 @@ export class SourceService {
|
|
|
3807
4386
|
details: {
|
|
3808
4387
|
artifactId,
|
|
3809
4388
|
nextAction: "Use resolve-artifact to resolve a source artifact first.",
|
|
3810
|
-
suggestedCall: {
|
|
4389
|
+
suggestedCall: {
|
|
4390
|
+
tool: "resolve-artifact",
|
|
4391
|
+
params: buildResolveArtifactParams({ kind: "version", value: "latest" })
|
|
4392
|
+
}
|
|
3811
4393
|
}
|
|
3812
4394
|
});
|
|
3813
4395
|
}
|
|
@@ -3825,8 +4407,9 @@ export class SourceService {
|
|
|
3825
4407
|
});
|
|
3826
4408
|
if (existing && reason === "already_current") {
|
|
3827
4409
|
this.metrics.recordArtifactCacheHit();
|
|
3828
|
-
|
|
3829
|
-
this.
|
|
4410
|
+
const touchedAt = new Date().toISOString();
|
|
4411
|
+
this.artifactsRepo.touchArtifact(resolved.artifactId, touchedAt);
|
|
4412
|
+
this.touchCacheMetrics(resolved.artifactId, touchedAt);
|
|
3830
4413
|
return;
|
|
3831
4414
|
}
|
|
3832
4415
|
this.metrics.recordArtifactCacheMiss();
|
|
@@ -3837,7 +4420,6 @@ export class SourceService {
|
|
|
3837
4420
|
});
|
|
3838
4421
|
await this.rebuildAndPersistArtifactIndex(resolved, reason === "already_current" ? "missing_meta" : reason);
|
|
3839
4422
|
this.enforceCacheLimits();
|
|
3840
|
-
this.refreshCacheMetrics();
|
|
3841
4423
|
}
|
|
3842
4424
|
async loadFromSourceJar(sourceJarPath) {
|
|
3843
4425
|
const files = [];
|
|
@@ -3855,21 +4437,22 @@ export class SourceService {
|
|
|
3855
4437
|
return this.filesRepo.listFiles(artifactId, { limit: 1 }).items.length > 0;
|
|
3856
4438
|
}
|
|
3857
4439
|
enforceCacheLimits() {
|
|
3858
|
-
let artifactCount = this.
|
|
3859
|
-
let totalBytes = this.
|
|
4440
|
+
let artifactCount = this.cacheMetricsState.entries;
|
|
4441
|
+
let totalBytes = this.cacheMetricsState.totalContentBytes;
|
|
3860
4442
|
if (artifactCount <= this.config.maxArtifacts && totalBytes <= this.config.maxCacheBytes) {
|
|
3861
4443
|
return;
|
|
3862
4444
|
}
|
|
3863
|
-
const candidates = this.
|
|
4445
|
+
const candidates = [...this.cacheMetricsState.lru];
|
|
3864
4446
|
for (const candidate of candidates) {
|
|
3865
4447
|
const shouldEvict = artifactCount > this.config.maxArtifacts || totalBytes > this.config.maxCacheBytes;
|
|
3866
4448
|
if (!shouldEvict || artifactCount <= 1) {
|
|
3867
|
-
|
|
4449
|
+
break;
|
|
3868
4450
|
}
|
|
3869
4451
|
const artifactCountBefore = artifactCount;
|
|
3870
4452
|
const totalBytesBefore = totalBytes;
|
|
3871
4453
|
this.filesRepo.deleteFilesForArtifact(candidate.artifactId);
|
|
3872
4454
|
this.artifactsRepo.deleteArtifact(candidate.artifactId);
|
|
4455
|
+
this.removeCacheMetrics(candidate.artifactId, false);
|
|
3873
4456
|
artifactCount = Math.max(0, artifactCount - 1);
|
|
3874
4457
|
totalBytes = Math.max(0, totalBytes - candidate.totalContentBytes);
|
|
3875
4458
|
this.metrics.recordCacheEviction();
|
|
@@ -3880,6 +4463,7 @@ export class SourceService {
|
|
|
3880
4463
|
artifactBytes: candidate.totalContentBytes
|
|
3881
4464
|
});
|
|
3882
4465
|
}
|
|
4466
|
+
this.publishCacheMetrics();
|
|
3883
4467
|
}
|
|
3884
4468
|
refreshCacheMetrics() {
|
|
3885
4469
|
const cacheEntries = this.artifactsRepo.countArtifacts();
|
|
@@ -3887,13 +4471,83 @@ export class SourceService {
|
|
|
3887
4471
|
const lruAccounting = this.artifactsRepo
|
|
3888
4472
|
.listArtifactsByLruWithContentBytes(Math.max(cacheEntries, 1))
|
|
3889
4473
|
.map((row) => ({
|
|
4474
|
+
artifactId: row.artifactId,
|
|
4475
|
+
totalContentBytes: row.totalContentBytes,
|
|
4476
|
+
updatedAt: row.updatedAt
|
|
4477
|
+
}));
|
|
4478
|
+
this.cacheMetricsState = {
|
|
4479
|
+
entries: cacheEntries,
|
|
4480
|
+
totalContentBytes,
|
|
4481
|
+
lru: lruAccounting
|
|
4482
|
+
};
|
|
4483
|
+
this.publishCacheMetrics();
|
|
4484
|
+
}
|
|
4485
|
+
touchCacheMetrics(artifactId, updatedAt) {
|
|
4486
|
+
const existingIndex = this.cacheMetricsState.lru.findIndex((row) => row.artifactId === artifactId);
|
|
4487
|
+
if (existingIndex < 0) {
|
|
4488
|
+
this.refreshCacheMetrics();
|
|
4489
|
+
return;
|
|
4490
|
+
}
|
|
4491
|
+
const [existing] = this.cacheMetricsState.lru.splice(existingIndex, 1);
|
|
4492
|
+
if (!existing) {
|
|
4493
|
+
this.refreshCacheMetrics();
|
|
4494
|
+
return;
|
|
4495
|
+
}
|
|
4496
|
+
existing.updatedAt = updatedAt;
|
|
4497
|
+
this.cacheMetricsState.lru.push(existing);
|
|
4498
|
+
this.publishCacheMetrics();
|
|
4499
|
+
}
|
|
4500
|
+
upsertCacheMetrics(artifactId, totalContentBytes, updatedAt) {
|
|
4501
|
+
const normalizedBytes = Math.max(0, Math.trunc(totalContentBytes));
|
|
4502
|
+
const existingIndex = this.cacheMetricsState.lru.findIndex((row) => row.artifactId === artifactId);
|
|
4503
|
+
if (existingIndex >= 0) {
|
|
4504
|
+
const [existing] = this.cacheMetricsState.lru.splice(existingIndex, 1);
|
|
4505
|
+
if (!existing) {
|
|
4506
|
+
this.refreshCacheMetrics();
|
|
4507
|
+
return;
|
|
4508
|
+
}
|
|
4509
|
+
this.cacheMetricsState.totalContentBytes = Math.max(0, this.cacheMetricsState.totalContentBytes - existing.totalContentBytes + normalizedBytes);
|
|
4510
|
+
existing.totalContentBytes = normalizedBytes;
|
|
4511
|
+
existing.updatedAt = updatedAt;
|
|
4512
|
+
this.cacheMetricsState.lru.push(existing);
|
|
4513
|
+
}
|
|
4514
|
+
else {
|
|
4515
|
+
this.cacheMetricsState.entries += 1;
|
|
4516
|
+
this.cacheMetricsState.totalContentBytes += normalizedBytes;
|
|
4517
|
+
this.cacheMetricsState.lru.push({
|
|
4518
|
+
artifactId,
|
|
4519
|
+
totalContentBytes: normalizedBytes,
|
|
4520
|
+
updatedAt
|
|
4521
|
+
});
|
|
4522
|
+
}
|
|
4523
|
+
this.cacheMetricsState.entries = this.cacheMetricsState.lru.length;
|
|
4524
|
+
this.publishCacheMetrics();
|
|
4525
|
+
}
|
|
4526
|
+
removeCacheMetrics(artifactId, publish = true) {
|
|
4527
|
+
const existingIndex = this.cacheMetricsState.lru.findIndex((row) => row.artifactId === artifactId);
|
|
4528
|
+
if (existingIndex < 0) {
|
|
4529
|
+
this.refreshCacheMetrics();
|
|
4530
|
+
return;
|
|
4531
|
+
}
|
|
4532
|
+
const [existing] = this.cacheMetricsState.lru.splice(existingIndex, 1);
|
|
4533
|
+
if (!existing) {
|
|
4534
|
+
this.refreshCacheMetrics();
|
|
4535
|
+
return;
|
|
4536
|
+
}
|
|
4537
|
+
this.cacheMetricsState.entries = this.cacheMetricsState.lru.length;
|
|
4538
|
+
this.cacheMetricsState.totalContentBytes = Math.max(0, this.cacheMetricsState.totalContentBytes - existing.totalContentBytes);
|
|
4539
|
+
if (publish) {
|
|
4540
|
+
this.publishCacheMetrics();
|
|
4541
|
+
}
|
|
4542
|
+
}
|
|
4543
|
+
publishCacheMetrics() {
|
|
4544
|
+
this.metrics.setCacheEntries(this.cacheMetricsState.entries);
|
|
4545
|
+
this.metrics.setCacheTotalContentBytes(this.cacheMetricsState.totalContentBytes);
|
|
4546
|
+
this.metrics.setCacheArtifactByteAccounting(this.cacheMetricsState.lru.map((row) => ({
|
|
3890
4547
|
artifact_id: row.artifactId,
|
|
3891
4548
|
content_bytes: row.totalContentBytes,
|
|
3892
4549
|
updated_at: row.updatedAt
|
|
3893
|
-
}));
|
|
3894
|
-
this.metrics.setCacheEntries(cacheEntries);
|
|
3895
|
-
this.metrics.setCacheTotalContentBytes(totalContentBytes);
|
|
3896
|
-
this.metrics.setCacheArtifactByteAccounting(lruAccounting);
|
|
4550
|
+
})));
|
|
3897
4551
|
}
|
|
3898
4552
|
}
|
|
3899
4553
|
//# sourceMappingURL=source-service.js.map
|