@adhisang/minecraft-modding-mcp 4.0.0 → 4.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 +61 -0
- package/README.md +40 -23
- package/dist/build-suggested-call.d.ts +29 -0
- package/dist/build-suggested-call.js +58 -0
- package/dist/cache-registry.d.ts +3 -1
- package/dist/cache-registry.js +50 -6
- package/dist/entry-tools/analyze-symbol-service.d.ts +16 -16
- package/dist/entry-tools/batch-class-members-service.d.ts +34 -0
- package/dist/entry-tools/batch-class-members-service.js +97 -0
- package/dist/entry-tools/batch-class-source-service.d.ts +37 -0
- package/dist/entry-tools/batch-class-source-service.js +100 -0
- package/dist/entry-tools/batch-mappings-service.d.ts +36 -0
- package/dist/entry-tools/batch-mappings-service.js +66 -0
- package/dist/entry-tools/batch-runner.d.ts +72 -0
- package/dist/entry-tools/batch-runner.js +90 -0
- package/dist/entry-tools/batch-symbol-exists-service.d.ts +46 -0
- package/dist/entry-tools/batch-symbol-exists-service.js +113 -0
- package/dist/entry-tools/compare-minecraft-service.d.ts +6 -6
- package/dist/entry-tools/inspect-minecraft/handlers/artifact.d.ts +5 -0
- package/dist/entry-tools/inspect-minecraft/handlers/artifact.js +83 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-members.d.ts +6 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-members.js +80 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-overview.d.ts +5 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-overview.js +248 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-source.d.ts +5 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-source.js +60 -0
- package/dist/entry-tools/inspect-minecraft/handlers/file.d.ts +5 -0
- package/dist/entry-tools/inspect-minecraft/handlers/file.js +54 -0
- package/dist/entry-tools/inspect-minecraft/handlers/list-files.d.ts +5 -0
- package/dist/entry-tools/inspect-minecraft/handlers/list-files.js +100 -0
- package/dist/entry-tools/inspect-minecraft/handlers/search.d.ts +5 -0
- package/dist/entry-tools/inspect-minecraft/handlers/search.js +155 -0
- package/dist/entry-tools/inspect-minecraft/handlers/versions.d.ts +6 -0
- package/dist/entry-tools/inspect-minecraft/handlers/versions.js +49 -0
- package/dist/entry-tools/inspect-minecraft/internal.d.ts +1042 -0
- package/dist/entry-tools/inspect-minecraft/internal.js +448 -0
- package/dist/entry-tools/inspect-minecraft-service.d.ts +193 -308
- package/dist/entry-tools/inspect-minecraft-service.js +20 -1244
- package/dist/entry-tools/manage-cache-service.d.ts +16 -16
- package/dist/entry-tools/validate-project/cases/access-transformer.d.ts +6 -0
- package/dist/entry-tools/validate-project/cases/access-transformer.js +106 -0
- package/dist/entry-tools/validate-project/cases/access-widener.d.ts +6 -0
- package/dist/entry-tools/validate-project/cases/access-widener.js +86 -0
- package/dist/entry-tools/validate-project/cases/mixin.d.ts +6 -0
- package/dist/entry-tools/validate-project/cases/mixin.js +90 -0
- package/dist/entry-tools/validate-project/cases/project-summary.d.ts +102 -0
- package/dist/entry-tools/validate-project/cases/project-summary.js +415 -0
- package/dist/entry-tools/validate-project/internal.d.ts +142 -0
- package/dist/entry-tools/validate-project/internal.js +303 -0
- package/dist/entry-tools/validate-project-service.d.ts +67 -47
- package/dist/entry-tools/validate-project-service.js +13 -563
- package/dist/entry-tools/verify-mixin-target-service.d.ts +133 -0
- package/dist/entry-tools/verify-mixin-target-service.js +323 -0
- package/dist/error-mapping.d.ts +40 -0
- package/dist/error-mapping.js +139 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +6 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +147 -1354
- package/dist/mapping/internal-types.d.ts +54 -0
- package/dist/mapping/internal-types.js +14 -0
- package/dist/mapping/loaders/mojang.d.ts +2 -0
- package/dist/mapping/loaders/mojang.js +64 -0
- package/dist/mapping/loaders/tiny-loom.d.ts +2 -0
- package/dist/mapping/loaders/tiny-loom.js +73 -0
- package/dist/mapping/loaders/tiny-maven.d.ts +2 -0
- package/dist/mapping/loaders/tiny-maven.js +104 -0
- package/dist/mapping/loaders/types.d.ts +14 -0
- package/dist/mapping/loaders/types.js +2 -0
- package/dist/mapping/lookup.d.ts +52 -0
- package/dist/mapping/lookup.js +496 -0
- package/dist/mapping/parsers/normalize.d.ts +10 -0
- package/dist/mapping/parsers/normalize.js +52 -0
- package/dist/mapping/parsers/proguard.d.ts +20 -0
- package/dist/mapping/parsers/proguard.js +138 -0
- package/dist/mapping/parsers/symbol-records.d.ts +27 -0
- package/dist/mapping/parsers/symbol-records.js +216 -0
- package/dist/mapping/parsers/tiny.d.ts +9 -0
- package/dist/mapping/parsers/tiny.js +96 -0
- package/dist/mapping/types.d.ts +147 -0
- package/dist/mapping/types.js +2 -0
- package/dist/mapping-pipeline-service.js +3 -2
- package/dist/mapping-service.d.ts +8 -145
- package/dist/mapping-service.js +30 -1207
- package/dist/mixin/access-validators.d.ts +9 -0
- package/dist/mixin/access-validators.js +257 -0
- package/dist/mixin/annotation-validators.d.ts +5 -0
- package/dist/mixin/annotation-validators.js +162 -0
- package/dist/mixin/helpers.d.ts +28 -0
- package/dist/mixin/helpers.js +315 -0
- package/dist/mixin/parsed-validator.d.ts +8 -0
- package/dist/mixin/parsed-validator.js +337 -0
- package/dist/mixin/types.d.ts +208 -0
- package/dist/mixin/types.js +28 -0
- package/dist/mixin-validator.d.ts +9 -201
- package/dist/mixin-validator.js +8 -1020
- package/dist/source/access-validate.d.ts +4 -0
- package/dist/source/access-validate.js +254 -0
- package/dist/source/artifact-resolver.d.ts +111 -0
- package/dist/source/artifact-resolver.js +1271 -0
- package/dist/source/cache-metrics.d.ts +26 -0
- package/dist/source/cache-metrics.js +172 -0
- package/dist/source/class-source/members-builder.d.ts +34 -0
- package/dist/source/class-source/members-builder.js +46 -0
- package/dist/source/class-source/snippet-builder.d.ts +19 -0
- package/dist/source/class-source/snippet-builder.js +46 -0
- package/dist/source/class-source-helpers.d.ts +34 -0
- package/dist/source/class-source-helpers.js +140 -0
- package/dist/source/class-source.d.ts +42 -0
- package/dist/source/class-source.js +883 -0
- package/dist/source/descriptor-utils.d.ts +6 -0
- package/dist/source/descriptor-utils.js +37 -0
- package/dist/source/file-access.d.ts +4 -0
- package/dist/source/file-access.js +102 -0
- package/dist/source/indexer.d.ts +82 -0
- package/dist/source/indexer.js +522 -0
- package/dist/source/lifecycle/diff-utils.d.ts +9 -0
- package/dist/source/lifecycle/diff-utils.js +107 -0
- package/dist/source/lifecycle/diff.d.ts +2 -0
- package/dist/source/lifecycle/diff.js +265 -0
- package/dist/source/lifecycle/mapping-helpers.d.ts +22 -0
- package/dist/source/lifecycle/mapping-helpers.js +327 -0
- package/dist/source/lifecycle/runtime-check.d.ts +2 -0
- package/dist/source/lifecycle/runtime-check.js +142 -0
- package/dist/source/lifecycle/trace.d.ts +2 -0
- package/dist/source/lifecycle/trace.js +231 -0
- package/dist/source/lifecycle.d.ts +4 -0
- package/dist/source/lifecycle.js +5 -0
- package/dist/source/search.d.ts +51 -0
- package/dist/source/search.js +676 -0
- package/dist/source/shared-utils.d.ts +6 -0
- package/dist/source/shared-utils.js +55 -0
- package/dist/source/state.d.ts +26 -0
- package/dist/source/state.js +24 -0
- package/dist/source/symbol-resolver.d.ts +3 -0
- package/dist/source/symbol-resolver.js +212 -0
- package/dist/source/validate-mixin/pipeline/mapping-health.d.ts +3 -0
- package/dist/source/validate-mixin/pipeline/mapping-health.js +41 -0
- package/dist/source/validate-mixin/pipeline/parse.d.ts +2 -0
- package/dist/source/validate-mixin/pipeline/parse.js +10 -0
- package/dist/source/validate-mixin/pipeline/resolve.d.ts +3 -0
- package/dist/source/validate-mixin/pipeline/resolve.js +78 -0
- package/dist/source/validate-mixin/pipeline/target-lookup.d.ts +6 -0
- package/dist/source/validate-mixin/pipeline/target-lookup.js +260 -0
- package/dist/source/validate-mixin/pipeline-context.d.ts +72 -0
- package/dist/source/validate-mixin/pipeline-context.js +93 -0
- package/dist/source/validate-mixin.d.ts +22 -0
- package/dist/source/validate-mixin.js +799 -0
- package/dist/source/workspace-target.d.ts +18 -0
- package/dist/source/workspace-target.js +305 -0
- package/dist/source-resolver.d.ts +1 -0
- package/dist/source-resolver.js +1 -1
- package/dist/source-service.d.ts +164 -170
- package/dist/source-service.js +70 -6116
- package/dist/stage-emitter.d.ts +13 -0
- package/dist/stage-emitter.js +30 -0
- package/dist/stdio-supervisor.d.ts +61 -0
- package/dist/stdio-supervisor.js +326 -9
- package/dist/tool-contract-manifest.d.ts +1 -1
- package/dist/tool-contract-manifest.js +23 -6
- package/dist/tool-guidance.d.ts +82 -0
- package/dist/tool-guidance.js +734 -0
- package/dist/tool-schema-registry.d.ts +16 -0
- package/dist/tool-schema-registry.js +37 -0
- package/dist/tool-schemas.d.ts +3518 -0
- package/dist/tool-schemas.js +813 -0
- package/dist/types.d.ts +36 -0
- package/dist/version-service.js +7 -6
- package/dist/workspace-context-cache.d.ts +32 -0
- package/dist/workspace-context-cache.js +66 -0
- package/dist/workspace-mapping-service.d.ts +16 -0
- package/dist/workspace-mapping-service.js +173 -1
- package/docs/README-ja.md +416 -0
- package/docs/examples.md +483 -0
- package/docs/tool-reference.md +462 -0
- package/package.json +17 -4
|
@@ -0,0 +1,799 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, dirname, resolve as resolvePath } from "node:path";
|
|
3
|
+
import { performance } from "node:perf_hooks";
|
|
4
|
+
import fastGlob from "fast-glob";
|
|
5
|
+
import { ERROR_CODES, createError, isAppError } from "../errors.js";
|
|
6
|
+
import { loadMixinStageBudgets, refreshMixinValidationOutcome, validateParsedMixin } from "../mixin-validator.js";
|
|
7
|
+
import { normalizePathForHost } from "../path-converter.js";
|
|
8
|
+
import { NOOP_STAGE_EMITTER } from "../stage-emitter.js";
|
|
9
|
+
import { createMixinPipelineContext } from "./validate-mixin/pipeline-context.js";
|
|
10
|
+
import { runResolveStage } from "./validate-mixin/pipeline/resolve.js";
|
|
11
|
+
import { runMappingHealthStage } from "./validate-mixin/pipeline/mapping-health.js";
|
|
12
|
+
import { runParseStage } from "./validate-mixin/pipeline/parse.js";
|
|
13
|
+
import { runTargetLookupStage } from "./validate-mixin/pipeline/target-lookup.js";
|
|
14
|
+
import { normalizePathStyle, pathExists } from "./shared-utils.js";
|
|
15
|
+
const COMMON_SOURCE_ROOTS = [
|
|
16
|
+
"src/main/java",
|
|
17
|
+
"src/client/java",
|
|
18
|
+
"common/src/main/java",
|
|
19
|
+
"common/src/client/java",
|
|
20
|
+
"fabric/src/main/java",
|
|
21
|
+
"fabric/src/client/java",
|
|
22
|
+
"neoforge/src/main/java",
|
|
23
|
+
"neoforge/src/client/java",
|
|
24
|
+
"forge/src/main/java",
|
|
25
|
+
"forge/src/client/java",
|
|
26
|
+
"quilt/src/main/java",
|
|
27
|
+
"quilt/src/client/java"
|
|
28
|
+
];
|
|
29
|
+
const MIXIN_PROJECT_DISCOVERY_IGNORES = [
|
|
30
|
+
"**/.git/**",
|
|
31
|
+
"**/.gradle/**",
|
|
32
|
+
"**/build/**",
|
|
33
|
+
"**/out/**",
|
|
34
|
+
"**/node_modules/**"
|
|
35
|
+
];
|
|
36
|
+
function annotateValidateMixinError(err, stage) {
|
|
37
|
+
if (isAppError(err)) {
|
|
38
|
+
const existing = (err.details ?? {});
|
|
39
|
+
if (typeof existing.failedStage === "string") {
|
|
40
|
+
return err;
|
|
41
|
+
}
|
|
42
|
+
return createError({
|
|
43
|
+
code: err.code,
|
|
44
|
+
message: err.message,
|
|
45
|
+
details: { ...existing, failedStage: stage }
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
49
|
+
return createError({
|
|
50
|
+
code: ERROR_CODES.INTERNAL,
|
|
51
|
+
message: `validate-mixin failed during stage "${stage}": ${message}`,
|
|
52
|
+
details: { failedStage: stage }
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function normalizeRequestedArtifactScope(scope) {
|
|
56
|
+
return scope ?? "vanilla";
|
|
57
|
+
}
|
|
58
|
+
function inferAppliedArtifactScope(input) {
|
|
59
|
+
if (input.scopeFallback?.applied === "vanilla") {
|
|
60
|
+
return "vanilla";
|
|
61
|
+
}
|
|
62
|
+
if (input.requestedScope === "vanilla") {
|
|
63
|
+
return "vanilla";
|
|
64
|
+
}
|
|
65
|
+
const joinedPath = `${normalizePathStyle(input.jarPath)} ${normalizePathStyle(input.resolvedSourceJarPath ?? "")}`.toLowerCase();
|
|
66
|
+
if (joinedPath.includes("minecraft-merged")) {
|
|
67
|
+
return "merged";
|
|
68
|
+
}
|
|
69
|
+
if (input.requestedScope === "loader" && joinedPath.includes("merged")) {
|
|
70
|
+
return "merged";
|
|
71
|
+
}
|
|
72
|
+
return input.requestedScope;
|
|
73
|
+
}
|
|
74
|
+
function scopeToJarType(scope) {
|
|
75
|
+
if (scope === "vanilla") {
|
|
76
|
+
return "vanilla-client";
|
|
77
|
+
}
|
|
78
|
+
return scope;
|
|
79
|
+
}
|
|
80
|
+
function sameStringArray(left, right) {
|
|
81
|
+
if (left === right)
|
|
82
|
+
return true;
|
|
83
|
+
if (!left || !right)
|
|
84
|
+
return false;
|
|
85
|
+
if (left.length !== right.length)
|
|
86
|
+
return false;
|
|
87
|
+
for (let i = 0; i < left.length; i++) {
|
|
88
|
+
if (left[i] !== right[i])
|
|
89
|
+
return false;
|
|
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
|
+
}
|
|
149
|
+
export async function validateMixin(svc, input, options = {}) {
|
|
150
|
+
// Wrap the dispatcher so any untagged AppError coming out of path
|
|
151
|
+
// normalization, preflight discovery, or config resolution surfaces with a
|
|
152
|
+
// meaningful failedStage. annotateValidateMixinError preserves any
|
|
153
|
+
// inner-pipeline tag (resolve/mapping-health/parse/target-lookup) so only
|
|
154
|
+
// the dispatcher-level errors default to input-validation.
|
|
155
|
+
try {
|
|
156
|
+
return await runValidateMixinDispatcher(svc, input, options);
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
throw annotateValidateMixinError(err, "input-validation");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async function runValidateMixinDispatcher(svc, input, options = {}) {
|
|
163
|
+
const { input: sourceInput, ...sharedInput } = input;
|
|
164
|
+
const mode = sourceInput.mode;
|
|
165
|
+
const stageEmitter = options.stageEmitter ?? NOOP_STAGE_EMITTER;
|
|
166
|
+
const sharedSingleOptions = {
|
|
167
|
+
stageEmitter,
|
|
168
|
+
__stageBudgets: options.__stageBudgets,
|
|
169
|
+
__testHooks: options.__testHooks
|
|
170
|
+
};
|
|
171
|
+
if (mode === "inline") {
|
|
172
|
+
const singleResult = await svc.validateMixinSingle({
|
|
173
|
+
...sharedInput,
|
|
174
|
+
source: sourceInput.source,
|
|
175
|
+
...sharedSingleOptions
|
|
176
|
+
});
|
|
177
|
+
return applyValidateMixinOutputCompaction(buildValidateMixinOutput(mode, [
|
|
178
|
+
{
|
|
179
|
+
source: {
|
|
180
|
+
kind: "inline",
|
|
181
|
+
label: "<inline>"
|
|
182
|
+
},
|
|
183
|
+
result: singleResult
|
|
184
|
+
}
|
|
185
|
+
]), input);
|
|
186
|
+
}
|
|
187
|
+
if (mode === "path") {
|
|
188
|
+
const resolvedPath = resolveMixinInputPath(sourceInput.path, "path");
|
|
189
|
+
const singleResult = await svc.validateMixinSingle({
|
|
190
|
+
...sharedInput,
|
|
191
|
+
sourcePath: sourceInput.path,
|
|
192
|
+
...sharedSingleOptions
|
|
193
|
+
});
|
|
194
|
+
return applyValidateMixinOutputCompaction(buildValidateMixinOutput(mode, [
|
|
195
|
+
{
|
|
196
|
+
source: {
|
|
197
|
+
kind: "path",
|
|
198
|
+
label: resolvedPath,
|
|
199
|
+
path: resolvedPath
|
|
200
|
+
},
|
|
201
|
+
result: singleResult
|
|
202
|
+
}
|
|
203
|
+
]), input);
|
|
204
|
+
}
|
|
205
|
+
if (mode === "paths") {
|
|
206
|
+
return validateMixinMany(svc, mode, sourceInput.paths.map((path) => ({
|
|
207
|
+
source: {
|
|
208
|
+
kind: "path",
|
|
209
|
+
label: resolveMixinInputPath(path, "path"),
|
|
210
|
+
path: resolveMixinInputPath(path, "path")
|
|
211
|
+
},
|
|
212
|
+
sourcePath: path
|
|
213
|
+
})), input, [], sharedSingleOptions);
|
|
214
|
+
}
|
|
215
|
+
const resolvedInput = mode === "project"
|
|
216
|
+
? await createProjectValidateMixinConfigInput(input)
|
|
217
|
+
: input;
|
|
218
|
+
const { sources: configSources, warnings: configWarnings } = await resolveMixinConfigSources(resolvedInput);
|
|
219
|
+
if (configSources.length === 0) {
|
|
220
|
+
const emptyOutput = buildValidateMixinOutput(mode, []);
|
|
221
|
+
return applyValidateMixinOutputCompaction({
|
|
222
|
+
...emptyOutput,
|
|
223
|
+
warnings: [...new Set([...emptyOutput.warnings, ...configWarnings])]
|
|
224
|
+
}, input);
|
|
225
|
+
}
|
|
226
|
+
return validateMixinMany(svc, mode, configSources.map((entry) => ({
|
|
227
|
+
source: {
|
|
228
|
+
kind: "config",
|
|
229
|
+
label: entry.sourcePath,
|
|
230
|
+
path: entry.sourcePath,
|
|
231
|
+
configPath: entry.configPath
|
|
232
|
+
},
|
|
233
|
+
sourcePath: entry.sourcePath
|
|
234
|
+
})), resolvedInput, configWarnings, sharedSingleOptions);
|
|
235
|
+
}
|
|
236
|
+
async function createProjectValidateMixinConfigInput(input) {
|
|
237
|
+
if (input.input.mode !== "project") {
|
|
238
|
+
return input;
|
|
239
|
+
}
|
|
240
|
+
const resolvedProjectPath = resolveMixinInputPath(input.input.path, "path");
|
|
241
|
+
const configPaths = (await fastGlob.glob(["**/*.mixins.json"], {
|
|
242
|
+
cwd: resolvedProjectPath,
|
|
243
|
+
absolute: true,
|
|
244
|
+
onlyFiles: true,
|
|
245
|
+
ignore: [...MIXIN_PROJECT_DISCOVERY_IGNORES]
|
|
246
|
+
})).sort((left, right) => left.localeCompare(right));
|
|
247
|
+
if (configPaths.length === 0) {
|
|
248
|
+
throw createError({
|
|
249
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
250
|
+
message: `No mixin config JSON files were found under project path "${input.input.path}".`,
|
|
251
|
+
details: {
|
|
252
|
+
failedStage: "input-validation",
|
|
253
|
+
nextAction: "Use input.mode='config' with explicit configPaths[], or point input.path at the workspace root that contains *.mixins.json files."
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
...input,
|
|
259
|
+
projectPath: input.projectPath ?? resolvedProjectPath,
|
|
260
|
+
input: {
|
|
261
|
+
mode: "config",
|
|
262
|
+
configPaths
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function shouldRetryValidateMixinWithMavenFirst(svc, input, result) {
|
|
267
|
+
const initialPriority = input.retryState?.initialSourcePriority ?? input.sourcePriority ?? svc.config.mappingSourcePriority;
|
|
268
|
+
if (input.retryState?.attempted || initialPriority !== "loom-first") {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
if (result.validationStatus !== "partial") {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
// Budget-driven partials are the soft-deadline's final output; retrying
|
|
275
|
+
// under maven-first would re-run the same expensive pipeline.
|
|
276
|
+
if (result.summary.degradedReason !== undefined ||
|
|
277
|
+
(result.summary.targetsDeferredBudget ?? 0) > 0) {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
if (result.summary.membersSkipped > 0) {
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
return result.issues.some((issue) => issue.resolutionPath === "source-signature-unavailable" ||
|
|
284
|
+
issue.resolutionPath === "target-mapping-failed" ||
|
|
285
|
+
issue.resolutionPath === "member-remap-failed");
|
|
286
|
+
}
|
|
287
|
+
export async function validateMixinSingle(svc, input) {
|
|
288
|
+
// Start at input-validation so path normalization, file reads, and the
|
|
289
|
+
// simple guard checks all land under that stage.
|
|
290
|
+
let currentStage = "input-validation";
|
|
291
|
+
const stageBudgets = loadMixinStageBudgets(input.__stageBudgets);
|
|
292
|
+
const inputValidationStartedAt = performance.now();
|
|
293
|
+
try {
|
|
294
|
+
const version = input.version.trim();
|
|
295
|
+
const requestedScope = normalizeRequestedArtifactScope(input.scope);
|
|
296
|
+
const currentSourcePriority = input.sourcePriority ?? svc.config.mappingSourcePriority;
|
|
297
|
+
const initialSourcePriority = input.retryState?.initialSourcePriority ?? currentSourcePriority;
|
|
298
|
+
if (!version) {
|
|
299
|
+
throw createError({
|
|
300
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
301
|
+
message: "version must be non-empty.",
|
|
302
|
+
details: { failedStage: "input-validation" }
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
let source;
|
|
306
|
+
if (input.sourcePath) {
|
|
307
|
+
const normalizedSourcePath = normalizePathForHost(input.sourcePath, undefined, "sourcePath");
|
|
308
|
+
const resolvedSourcePath = isAbsolute(normalizedSourcePath)
|
|
309
|
+
? normalizedSourcePath
|
|
310
|
+
: resolvePath(process.cwd(), normalizedSourcePath);
|
|
311
|
+
try {
|
|
312
|
+
source = await readFile(resolvedSourcePath, "utf-8");
|
|
313
|
+
}
|
|
314
|
+
catch (err) {
|
|
315
|
+
throw createError({
|
|
316
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
317
|
+
message: `Could not read sourcePath "${input.sourcePath}" (resolved to "${resolvedSourcePath}"):` +
|
|
318
|
+
` ${err instanceof Error ? err.message : String(err)}`,
|
|
319
|
+
details: { failedStage: "input-validation" }
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
source = input.source ?? "";
|
|
325
|
+
}
|
|
326
|
+
if (!source.trim()) {
|
|
327
|
+
throw createError({
|
|
328
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
329
|
+
message: "source must be non-empty.",
|
|
330
|
+
details: { failedStage: "input-validation" }
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
const inputElapsed = performance.now() - inputValidationStartedAt;
|
|
334
|
+
if (inputElapsed > stageBudgets.inputValidation) {
|
|
335
|
+
throw createError({
|
|
336
|
+
code: ERROR_CODES.STAGE_BUDGET_PRE_PARSE,
|
|
337
|
+
message: "Stage input-validation exhausted budget before parse completed.",
|
|
338
|
+
details: {
|
|
339
|
+
failedStage: "input-validation",
|
|
340
|
+
stageBudgetExhausted: true,
|
|
341
|
+
budgetMs: stageBudgets.inputValidation,
|
|
342
|
+
elapsedMs: inputElapsed
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
return await runValidateMixinPipeline(svc, {
|
|
347
|
+
input,
|
|
348
|
+
version,
|
|
349
|
+
source,
|
|
350
|
+
requestedScope,
|
|
351
|
+
currentSourcePriority,
|
|
352
|
+
initialSourcePriority,
|
|
353
|
+
stageEmitter: input.stageEmitter ?? NOOP_STAGE_EMITTER,
|
|
354
|
+
stageBudgets,
|
|
355
|
+
testHooks: input.__testHooks,
|
|
356
|
+
onStage: (stage) => { currentStage = stage; }
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
catch (err) {
|
|
360
|
+
throw annotateValidateMixinError(err, currentStage);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
async function runValidateMixinPipeline(svc, seed) {
|
|
364
|
+
const ctx = createMixinPipelineContext(seed);
|
|
365
|
+
await runResolveStage(svc, ctx);
|
|
366
|
+
await runMappingHealthStage(svc, ctx);
|
|
367
|
+
await runParseStage(ctx);
|
|
368
|
+
await runTargetLookupStage(svc, ctx);
|
|
369
|
+
return finalizeValidateMixinPipeline(svc, ctx);
|
|
370
|
+
}
|
|
371
|
+
async function finalizeValidateMixinPipeline(svc, ctx) {
|
|
372
|
+
const { input, source, requestedScope, currentSourcePriority, initialSourcePriority } = ctx;
|
|
373
|
+
const resolutionNotes = [];
|
|
374
|
+
if (ctx.requestedMapping !== ctx.mappingApplied) {
|
|
375
|
+
resolutionNotes.push(`Mapping fallback: requested "${ctx.requestedMapping}" but applied "${ctx.mappingApplied}" due to remapping failure.`);
|
|
376
|
+
}
|
|
377
|
+
const appliedScope = inferAppliedArtifactScope({
|
|
378
|
+
requestedScope,
|
|
379
|
+
scopeFallback: ctx.scopeFallback,
|
|
380
|
+
jarPath: ctx.jarPath,
|
|
381
|
+
resolvedSourceJarPath: ctx.resolvedArtifact?.resolvedSourceJarPath
|
|
382
|
+
});
|
|
383
|
+
if (!ctx.scopeFallback && requestedScope !== appliedScope) {
|
|
384
|
+
resolutionNotes.push(`Scope adjusted during validation: requested "${requestedScope}" but resolved artifact looks like "${appliedScope}".`);
|
|
385
|
+
}
|
|
386
|
+
const REMAP_WARNING_RE = /^(?:Could not remap|Remap failed for)\b/;
|
|
387
|
+
const remapFailures = ctx.warnings.filter((w) => REMAP_WARNING_RE.test(w)).length;
|
|
388
|
+
let confidence = "definite";
|
|
389
|
+
if (ctx.requestedMapping !== ctx.mappingApplied) {
|
|
390
|
+
confidence = "uncertain";
|
|
391
|
+
}
|
|
392
|
+
else if (remapFailures > 0) {
|
|
393
|
+
confidence = "likely";
|
|
394
|
+
}
|
|
395
|
+
const mappingChain = [];
|
|
396
|
+
if (ctx.requestedMapping !== ctx.signatureLookupMapping) {
|
|
397
|
+
mappingChain.push(`${ctx.requestedMapping} → ${ctx.signatureLookupMapping}`);
|
|
398
|
+
}
|
|
399
|
+
if (ctx.mappingApplied !== ctx.signatureLookupMapping) {
|
|
400
|
+
mappingChain.push(`fallback to ${ctx.mappingApplied}`);
|
|
401
|
+
}
|
|
402
|
+
const provenance = {
|
|
403
|
+
version: ctx.version,
|
|
404
|
+
jarPath: ctx.jarPath,
|
|
405
|
+
requestedMapping: ctx.requestedMapping,
|
|
406
|
+
mappingApplied: ctx.mappingApplied,
|
|
407
|
+
requestedScope,
|
|
408
|
+
appliedScope,
|
|
409
|
+
requestedSourcePriority: initialSourcePriority,
|
|
410
|
+
appliedSourcePriority: currentSourcePriority,
|
|
411
|
+
resolutionNotes: resolutionNotes.length > 0 ? resolutionNotes : undefined,
|
|
412
|
+
jarType: scopeToJarType(appliedScope),
|
|
413
|
+
mappingChain: mappingChain.length > 0 ? mappingChain : undefined,
|
|
414
|
+
remapFailures: remapFailures > 0 ? remapFailures : undefined,
|
|
415
|
+
mappingAutoDetected: ctx.mappingAutoDetected || undefined,
|
|
416
|
+
scopeFallback: ctx.scopeFallback,
|
|
417
|
+
resolutionTrace: ctx.resolutionTrace && ctx.resolutionTrace.length > 0 ? ctx.resolutionTrace : undefined
|
|
418
|
+
};
|
|
419
|
+
const baseResult = validateParsedMixin(ctx.parsed, ctx.targetMembers, ctx.warnings, provenance, confidence, ctx.mappingFailedTargets, input.explain, ctx.remapFailedMembers, ctx.signatureFailedTargets, input.explain ? { scope: requestedScope, sourcePriority: currentSourcePriority, projectPath: input.projectPath, mapping: ctx.requestedMapping } : undefined, input.warningMode, ctx.healthReport, ctx.symbolExistsButSignatureFailed.size > 0 ? ctx.symbolExistsButSignatureFailed : undefined, ctx.skippedForValidator.size > 0 ? ctx.skippedForValidator : undefined);
|
|
420
|
+
if (ctx.targetOutcomes.length > 0) {
|
|
421
|
+
baseResult.targetOutcomes = ctx.targetOutcomes;
|
|
422
|
+
}
|
|
423
|
+
if (ctx.degradedReason !== undefined) {
|
|
424
|
+
baseResult.summary = { ...baseResult.summary, degradedReason: ctx.degradedReason };
|
|
425
|
+
}
|
|
426
|
+
if (ctx.deferredTargetClasses.size > 0) {
|
|
427
|
+
baseResult.summary = {
|
|
428
|
+
...baseResult.summary,
|
|
429
|
+
targetsDeferredBudget: ctx.deferredTargetClasses.size
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
const result = refreshMixinValidationOutcome(baseResult);
|
|
433
|
+
const minSeverity = input.minSeverity ?? "all";
|
|
434
|
+
const hideUncertain = input.hideUncertain ?? false;
|
|
435
|
+
if (minSeverity !== "all" || hideUncertain) {
|
|
436
|
+
const unfilteredSummary = { ...result.summary };
|
|
437
|
+
let filtered = result.issues;
|
|
438
|
+
if (minSeverity === "error") {
|
|
439
|
+
filtered = filtered.filter((i) => i.severity === "error");
|
|
440
|
+
}
|
|
441
|
+
else if (minSeverity === "warning") {
|
|
442
|
+
filtered = filtered.filter((i) => i.severity === "error" || i.severity === "warning");
|
|
443
|
+
}
|
|
444
|
+
if (hideUncertain) {
|
|
445
|
+
filtered = filtered.filter((i) => i.confidence !== "uncertain");
|
|
446
|
+
}
|
|
447
|
+
const filteredErrors = filtered.filter((i) => i.severity === "error").length;
|
|
448
|
+
const filteredWarnings = filtered.filter((i) => i.severity === "warning").length;
|
|
449
|
+
const filteredDefiniteErrors = filtered.filter((i) => i.severity === "error" && i.confidence !== "uncertain").length;
|
|
450
|
+
const filteredUncertainErrors = filtered.filter((i) => i.severity === "error" && i.confidence === "uncertain").length;
|
|
451
|
+
const filteredResolutionErrors = filtered.filter((i) => i.resolutionPath != null).length;
|
|
452
|
+
const filteredParseWarnings = filtered.filter((i) => i.category === "parse").length;
|
|
453
|
+
result.issues = filtered;
|
|
454
|
+
result.summary = {
|
|
455
|
+
...result.summary,
|
|
456
|
+
errors: filteredErrors,
|
|
457
|
+
warnings: filteredWarnings,
|
|
458
|
+
definiteErrors: filteredDefiniteErrors,
|
|
459
|
+
uncertainErrors: filteredUncertainErrors,
|
|
460
|
+
resolutionErrors: filteredResolutionErrors,
|
|
461
|
+
parseWarnings: filteredParseWarnings
|
|
462
|
+
};
|
|
463
|
+
result.unfilteredSummary = unfilteredSummary;
|
|
464
|
+
}
|
|
465
|
+
if (input.warningCategoryFilter && input.warningCategoryFilter.length > 0) {
|
|
466
|
+
const allowedCategories = new Set(input.warningCategoryFilter);
|
|
467
|
+
result.issues = result.issues.filter((i) => i.category && allowedCategories.has(i.category));
|
|
468
|
+
if (result.structuredWarnings) {
|
|
469
|
+
result.structuredWarnings = result.structuredWarnings.filter((sw) => sw.category && allowedCategories.has(sw.category));
|
|
470
|
+
if (result.structuredWarnings.length === 0)
|
|
471
|
+
result.structuredWarnings = undefined;
|
|
472
|
+
}
|
|
473
|
+
const catErrors = result.issues.filter((i) => i.severity === "error").length;
|
|
474
|
+
const catWarnings = result.issues.filter((i) => i.severity === "warning").length;
|
|
475
|
+
const catDefiniteErrors = result.issues.filter((i) => i.severity === "error" && i.confidence !== "uncertain").length;
|
|
476
|
+
result.summary = {
|
|
477
|
+
...result.summary,
|
|
478
|
+
errors: catErrors,
|
|
479
|
+
warnings: catWarnings,
|
|
480
|
+
definiteErrors: catDefiniteErrors,
|
|
481
|
+
uncertainErrors: result.issues.filter((i) => i.severity === "error" && i.confidence === "uncertain").length,
|
|
482
|
+
resolutionErrors: result.issues.filter((i) => i.resolutionPath != null).length,
|
|
483
|
+
parseWarnings: result.issues.filter((i) => i.category === "parse").length
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
if (input.treatInfoAsWarning === false && result.structuredWarnings) {
|
|
487
|
+
result.structuredWarnings = result.structuredWarnings.filter((sw) => sw.severity !== "info");
|
|
488
|
+
if (result.structuredWarnings.length === 0)
|
|
489
|
+
result.structuredWarnings = undefined;
|
|
490
|
+
}
|
|
491
|
+
if (input.reportMode === "compact") {
|
|
492
|
+
refreshMixinValidationOutcome(result);
|
|
493
|
+
result.resolvedMembers = undefined;
|
|
494
|
+
result.structuredWarnings = undefined;
|
|
495
|
+
result.aggregatedWarnings = undefined;
|
|
496
|
+
result.toolHealth = undefined;
|
|
497
|
+
result.confidenceBreakdown = undefined;
|
|
498
|
+
if (result.provenance) {
|
|
499
|
+
result.provenance.resolutionTrace = undefined;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
refreshMixinValidationOutcome(result);
|
|
504
|
+
}
|
|
505
|
+
if (shouldRetryValidateMixinWithMavenFirst(svc, input, result)) {
|
|
506
|
+
const retryWarning = `Retrying validate-mixin with sourcePriority="maven-first" after partial validation using "${currentSourcePriority}".`;
|
|
507
|
+
try {
|
|
508
|
+
const retried = await svc.validateMixinSingle({
|
|
509
|
+
...input,
|
|
510
|
+
source,
|
|
511
|
+
sourcePath: undefined,
|
|
512
|
+
sourcePriority: "maven-first",
|
|
513
|
+
retryState: {
|
|
514
|
+
attempted: true,
|
|
515
|
+
initialSourcePriority
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
retried.warnings = [retryWarning, ...retried.warnings];
|
|
519
|
+
if (retried.provenance) {
|
|
520
|
+
retried.provenance.requestedSourcePriority = initialSourcePriority;
|
|
521
|
+
retried.provenance.appliedSourcePriority = "maven-first";
|
|
522
|
+
retried.provenance.resolutionNotes = [
|
|
523
|
+
...(retried.provenance.resolutionNotes ?? []),
|
|
524
|
+
`Validation retried with sourcePriority "maven-first" after partial result from "${currentSourcePriority}".`
|
|
525
|
+
];
|
|
526
|
+
}
|
|
527
|
+
return retried;
|
|
528
|
+
}
|
|
529
|
+
catch (retryErr) {
|
|
530
|
+
result.warnings.unshift(`${retryWarning} Retry failed: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`);
|
|
531
|
+
return result;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return result;
|
|
535
|
+
}
|
|
536
|
+
function resolveMixinInputPath(rawPath, fieldName) {
|
|
537
|
+
const normalizedPath = normalizePathForHost(rawPath, undefined, fieldName);
|
|
538
|
+
return isAbsolute(normalizedPath)
|
|
539
|
+
? normalizedPath
|
|
540
|
+
: resolvePath(process.cwd(), normalizedPath);
|
|
541
|
+
}
|
|
542
|
+
async function resolveMixinConfigSources(input) {
|
|
543
|
+
if (input.input.mode !== "config") {
|
|
544
|
+
return {
|
|
545
|
+
sources: [],
|
|
546
|
+
warnings: []
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
const results = [];
|
|
550
|
+
const warnings = [];
|
|
551
|
+
for (const rawConfigPath of input.input.configPaths) {
|
|
552
|
+
const resolvedConfigPath = resolveMixinInputPath(rawConfigPath, "configPath");
|
|
553
|
+
let configJson;
|
|
554
|
+
try {
|
|
555
|
+
const raw = await readFile(resolvedConfigPath, "utf-8");
|
|
556
|
+
configJson = JSON.parse(raw);
|
|
557
|
+
}
|
|
558
|
+
catch (err) {
|
|
559
|
+
throw createError({
|
|
560
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
561
|
+
message: `Could not read/parse mixin config "${rawConfigPath}": ${err instanceof Error ? err.message : String(err)}`,
|
|
562
|
+
details: { failedStage: "input-validation" }
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
const pkg = configJson.package ?? "";
|
|
566
|
+
const classNames = [
|
|
567
|
+
...(configJson.mixins ?? []),
|
|
568
|
+
...(configJson.client ?? []),
|
|
569
|
+
...(configJson.server ?? [])
|
|
570
|
+
];
|
|
571
|
+
if (classNames.length === 0) {
|
|
572
|
+
warnings.push(`Mixin config "${resolvedConfigPath}" contains no mixin class entries.`);
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
const projectBase = input.projectPath
|
|
576
|
+
? (isAbsolute(input.projectPath) ? input.projectPath : resolvePath(process.cwd(), input.projectPath))
|
|
577
|
+
: dirname(resolvedConfigPath);
|
|
578
|
+
let sourceRootCandidates;
|
|
579
|
+
if (input.sourceRoots && input.sourceRoots.length > 0) {
|
|
580
|
+
sourceRootCandidates = input.sourceRoots;
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
const detected = [];
|
|
584
|
+
for (const candidateRoot of COMMON_SOURCE_ROOTS) {
|
|
585
|
+
let foundInRoot = false;
|
|
586
|
+
for (const className of classNames) {
|
|
587
|
+
const fqcn = pkg ? `${pkg}.${className}` : className;
|
|
588
|
+
const relative = fqcn.replace(/\./g, "/") + ".java";
|
|
589
|
+
if (await pathExists(resolvePath(projectBase, candidateRoot, relative))) {
|
|
590
|
+
foundInRoot = true;
|
|
591
|
+
break;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
if (foundInRoot) {
|
|
595
|
+
detected.push(candidateRoot);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
sourceRootCandidates = detected.length > 0 ? detected : ["src/main/java"];
|
|
599
|
+
}
|
|
600
|
+
for (const cls of classNames) {
|
|
601
|
+
const fqcn = pkg ? `${pkg}.${cls}` : cls;
|
|
602
|
+
const relativePath = fqcn.replace(/\./g, "/") + ".java";
|
|
603
|
+
let sourcePath = resolvePath(projectBase, sourceRootCandidates[0], relativePath);
|
|
604
|
+
for (const root of sourceRootCandidates) {
|
|
605
|
+
const candidate = resolvePath(projectBase, root, relativePath);
|
|
606
|
+
if (await pathExists(candidate)) {
|
|
607
|
+
sourcePath = candidate;
|
|
608
|
+
break;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
results.push({
|
|
612
|
+
sourcePath,
|
|
613
|
+
configPath: resolvedConfigPath
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
return {
|
|
618
|
+
sources: results,
|
|
619
|
+
warnings
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
async function validateMixinMany(svc, mode, entries, input, additionalWarnings, extras = {}) {
|
|
623
|
+
const results = [];
|
|
624
|
+
const batchWarningMode = input.warningMode ?? "aggregated";
|
|
625
|
+
const { input: _discardedInput, ...sharedInput } = input;
|
|
626
|
+
const batchCaches = {
|
|
627
|
+
classMappings: new Map()
|
|
628
|
+
};
|
|
629
|
+
const stageEmitter = extras.stageEmitter ?? NOOP_STAGE_EMITTER;
|
|
630
|
+
for (const entry of entries) {
|
|
631
|
+
try {
|
|
632
|
+
const singleResult = await svc.validateMixinSingle({
|
|
633
|
+
...sharedInput,
|
|
634
|
+
sourcePath: entry.sourcePath,
|
|
635
|
+
warningMode: batchWarningMode,
|
|
636
|
+
batchCaches,
|
|
637
|
+
stageEmitter,
|
|
638
|
+
__stageBudgets: extras.__stageBudgets,
|
|
639
|
+
__testHooks: extras.__testHooks
|
|
640
|
+
});
|
|
641
|
+
results.push({
|
|
642
|
+
source: entry.source,
|
|
643
|
+
result: singleResult
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
catch (err) {
|
|
647
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
648
|
+
const entryResult = {
|
|
649
|
+
source: entry.source,
|
|
650
|
+
error: message
|
|
651
|
+
};
|
|
652
|
+
if (isAppError(err)) {
|
|
653
|
+
entryResult.errorCode = err.code;
|
|
654
|
+
if (err.details) {
|
|
655
|
+
entryResult.errorDetails = { ...err.details };
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
results.push(entryResult);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
const output = buildValidateMixinOutput(mode, results);
|
|
662
|
+
return applyValidateMixinOutputCompaction({
|
|
663
|
+
...output,
|
|
664
|
+
warnings: additionalWarnings.length === 0
|
|
665
|
+
? output.warnings
|
|
666
|
+
: [...new Set([...output.warnings, ...additionalWarnings])]
|
|
667
|
+
}, input);
|
|
668
|
+
}
|
|
669
|
+
function applyValidateMixinOutputCompaction(output, input) {
|
|
670
|
+
let nextOutput = output;
|
|
671
|
+
const canHoistProvenance = nextOutput.provenance != null;
|
|
672
|
+
const warningCandidates = nextOutput.results
|
|
673
|
+
.map((entry) => entry.result?.warnings)
|
|
674
|
+
.filter((entry) => entry != null);
|
|
675
|
+
const canHoistWarnings = warningCandidates.length === 0
|
|
676
|
+
? true
|
|
677
|
+
: warningCandidates.every((entry) => sameStringArray(entry, warningCandidates[0]));
|
|
678
|
+
if (input.reportMode === "summary-first") {
|
|
679
|
+
nextOutput = {
|
|
680
|
+
...nextOutput,
|
|
681
|
+
results: nextOutput.results.map((entry) => (entry.result
|
|
682
|
+
? {
|
|
683
|
+
...entry,
|
|
684
|
+
result: {
|
|
685
|
+
...entry.result,
|
|
686
|
+
warnings: canHoistWarnings ? [] : entry.result.warnings,
|
|
687
|
+
structuredWarnings: undefined,
|
|
688
|
+
aggregatedWarnings: undefined,
|
|
689
|
+
resolvedMembers: undefined,
|
|
690
|
+
toolHealth: undefined,
|
|
691
|
+
confidenceBreakdown: undefined,
|
|
692
|
+
provenance: canHoistProvenance ? undefined : entry.result.provenance
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
: entry))
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
if (input.includeIssues !== false) {
|
|
699
|
+
return nextOutput;
|
|
700
|
+
}
|
|
701
|
+
return {
|
|
702
|
+
...nextOutput,
|
|
703
|
+
results: nextOutput.results.map((entry) => (entry.result
|
|
704
|
+
? {
|
|
705
|
+
...entry,
|
|
706
|
+
result: {
|
|
707
|
+
...entry.result,
|
|
708
|
+
issues: []
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
: entry))
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
function buildValidateMixinOutput(mode, results) {
|
|
715
|
+
let valid = 0;
|
|
716
|
+
let partial = 0;
|
|
717
|
+
let invalid = 0;
|
|
718
|
+
let processingErrors = 0;
|
|
719
|
+
let totalValidationErrors = 0;
|
|
720
|
+
let totalValidationWarnings = 0;
|
|
721
|
+
const warningSet = new Set();
|
|
722
|
+
const incompleteReasonSet = new Set();
|
|
723
|
+
const issueGroupMap = new Map();
|
|
724
|
+
for (const entry of results) {
|
|
725
|
+
if (!entry.result) {
|
|
726
|
+
processingErrors++;
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
if (entry.result.valid) {
|
|
730
|
+
valid++;
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
invalid++;
|
|
734
|
+
}
|
|
735
|
+
if (entry.result.validationStatus === "partial") {
|
|
736
|
+
partial++;
|
|
737
|
+
}
|
|
738
|
+
totalValidationErrors += entry.result.summary.errors;
|
|
739
|
+
totalValidationWarnings += entry.result.summary.warnings;
|
|
740
|
+
for (const warning of entry.result.warnings) {
|
|
741
|
+
warningSet.add(warning);
|
|
742
|
+
}
|
|
743
|
+
for (const issue of entry.result.issues) {
|
|
744
|
+
if (issue.kind === "validation-incomplete") {
|
|
745
|
+
incompleteReasonSet.add(`validation-incomplete: ${issue.message}`);
|
|
746
|
+
}
|
|
747
|
+
const key = `${issue.kind}\0${issue.confidence ?? "unknown"}\0${issue.category ?? "validation"}`;
|
|
748
|
+
const existing = issueGroupMap.get(key);
|
|
749
|
+
if (existing) {
|
|
750
|
+
existing.count++;
|
|
751
|
+
if (existing.sampleTargets.length < 3) {
|
|
752
|
+
existing.sampleTargets.push(issue.target);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
else {
|
|
756
|
+
issueGroupMap.set(key, {
|
|
757
|
+
kind: issue.kind,
|
|
758
|
+
confidence: issue.confidence ?? "unknown",
|
|
759
|
+
category: issue.category ?? "validation",
|
|
760
|
+
count: 1,
|
|
761
|
+
sampleTargets: [issue.target]
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
const issueSummary = issueGroupMap.size > 0 ? [...issueGroupMap.values()] : undefined;
|
|
767
|
+
const provenanceCandidates = results
|
|
768
|
+
.map((entry) => entry.result?.provenance)
|
|
769
|
+
.filter((entry) => entry != null);
|
|
770
|
+
const provenance = provenanceCandidates.length === 0
|
|
771
|
+
? undefined
|
|
772
|
+
: provenanceCandidates.every((entry) => sameMixinValidationProvenance(entry, provenanceCandidates[0]))
|
|
773
|
+
? provenanceCandidates[0]
|
|
774
|
+
: undefined;
|
|
775
|
+
const toolHealth = results.find((entry) => entry.result?.toolHealth)?.result?.toolHealth;
|
|
776
|
+
const confidenceScores = results
|
|
777
|
+
.map((entry) => entry.result?.confidenceScore)
|
|
778
|
+
.filter((score) => score != null);
|
|
779
|
+
return {
|
|
780
|
+
mode,
|
|
781
|
+
results,
|
|
782
|
+
summary: {
|
|
783
|
+
total: results.length,
|
|
784
|
+
valid,
|
|
785
|
+
partial,
|
|
786
|
+
invalid,
|
|
787
|
+
processingErrors,
|
|
788
|
+
totalValidationErrors,
|
|
789
|
+
totalValidationWarnings
|
|
790
|
+
},
|
|
791
|
+
issueSummary,
|
|
792
|
+
provenance,
|
|
793
|
+
incompleteReasons: incompleteReasonSet.size > 0 ? [...incompleteReasonSet] : undefined,
|
|
794
|
+
toolHealth,
|
|
795
|
+
confidenceScore: confidenceScores.length > 0 ? Math.min(...confidenceScores) : undefined,
|
|
796
|
+
warnings: [...warningSet]
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
//# sourceMappingURL=validate-mixin.js.map
|