@adhisang/minecraft-modding-mcp 3.2.0 → 4.1.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.
Files changed (194) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +52 -32
  3. package/dist/build-suggested-call.d.ts +29 -0
  4. package/dist/build-suggested-call.js +58 -0
  5. package/dist/cache-registry.d.ts +3 -1
  6. package/dist/cache-registry.js +59 -7
  7. package/dist/config.d.ts +10 -1
  8. package/dist/config.js +52 -1
  9. package/dist/entry-tools/analyze-symbol-service.d.ts +18 -18
  10. package/dist/entry-tools/analyze-symbol-service.js +13 -2
  11. package/dist/entry-tools/batch-class-members-service.d.ts +34 -0
  12. package/dist/entry-tools/batch-class-members-service.js +97 -0
  13. package/dist/entry-tools/batch-class-source-service.d.ts +37 -0
  14. package/dist/entry-tools/batch-class-source-service.js +100 -0
  15. package/dist/entry-tools/batch-mappings-service.d.ts +36 -0
  16. package/dist/entry-tools/batch-mappings-service.js +66 -0
  17. package/dist/entry-tools/batch-runner.d.ts +72 -0
  18. package/dist/entry-tools/batch-runner.js +90 -0
  19. package/dist/entry-tools/batch-symbol-exists-service.d.ts +46 -0
  20. package/dist/entry-tools/batch-symbol-exists-service.js +113 -0
  21. package/dist/entry-tools/compare-minecraft-service.d.ts +6 -6
  22. package/dist/entry-tools/inspect-minecraft/handlers/artifact.d.ts +5 -0
  23. package/dist/entry-tools/inspect-minecraft/handlers/artifact.js +83 -0
  24. package/dist/entry-tools/inspect-minecraft/handlers/class-members.d.ts +6 -0
  25. package/dist/entry-tools/inspect-minecraft/handlers/class-members.js +80 -0
  26. package/dist/entry-tools/inspect-minecraft/handlers/class-overview.d.ts +5 -0
  27. package/dist/entry-tools/inspect-minecraft/handlers/class-overview.js +248 -0
  28. package/dist/entry-tools/inspect-minecraft/handlers/class-source.d.ts +5 -0
  29. package/dist/entry-tools/inspect-minecraft/handlers/class-source.js +60 -0
  30. package/dist/entry-tools/inspect-minecraft/handlers/file.d.ts +5 -0
  31. package/dist/entry-tools/inspect-minecraft/handlers/file.js +54 -0
  32. package/dist/entry-tools/inspect-minecraft/handlers/list-files.d.ts +5 -0
  33. package/dist/entry-tools/inspect-minecraft/handlers/list-files.js +100 -0
  34. package/dist/entry-tools/inspect-minecraft/handlers/search.d.ts +5 -0
  35. package/dist/entry-tools/inspect-minecraft/handlers/search.js +155 -0
  36. package/dist/entry-tools/inspect-minecraft/handlers/versions.d.ts +6 -0
  37. package/dist/entry-tools/inspect-minecraft/handlers/versions.js +49 -0
  38. package/dist/entry-tools/inspect-minecraft/internal.d.ts +1042 -0
  39. package/dist/entry-tools/inspect-minecraft/internal.js +448 -0
  40. package/dist/entry-tools/inspect-minecraft-service.d.ts +213 -328
  41. package/dist/entry-tools/inspect-minecraft-service.js +20 -1238
  42. package/dist/entry-tools/manage-cache-service.d.ts +16 -16
  43. package/dist/entry-tools/validate-project/cases/access-transformer.d.ts +6 -0
  44. package/dist/entry-tools/validate-project/cases/access-transformer.js +106 -0
  45. package/dist/entry-tools/validate-project/cases/access-widener.d.ts +6 -0
  46. package/dist/entry-tools/validate-project/cases/access-widener.js +86 -0
  47. package/dist/entry-tools/validate-project/cases/mixin.d.ts +6 -0
  48. package/dist/entry-tools/validate-project/cases/mixin.js +90 -0
  49. package/dist/entry-tools/validate-project/cases/project-summary.d.ts +97 -0
  50. package/dist/entry-tools/validate-project/cases/project-summary.js +346 -0
  51. package/dist/entry-tools/validate-project/internal.d.ts +135 -0
  52. package/dist/entry-tools/validate-project/internal.js +287 -0
  53. package/dist/entry-tools/validate-project-service.d.ts +63 -47
  54. package/dist/entry-tools/validate-project-service.js +12 -482
  55. package/dist/entry-tools/verify-mixin-target-service.d.ts +133 -0
  56. package/dist/entry-tools/verify-mixin-target-service.js +323 -0
  57. package/dist/error-mapping.d.ts +40 -0
  58. package/dist/error-mapping.js +139 -0
  59. package/dist/errors.d.ts +6 -0
  60. package/dist/errors.js +6 -0
  61. package/dist/index.d.ts +2 -0
  62. package/dist/index.js +170 -1314
  63. package/dist/lru-list.d.ts +31 -0
  64. package/dist/lru-list.js +102 -0
  65. package/dist/mapping/internal-types.d.ts +54 -0
  66. package/dist/mapping/internal-types.js +14 -0
  67. package/dist/mapping/loaders/mojang.d.ts +2 -0
  68. package/dist/mapping/loaders/mojang.js +64 -0
  69. package/dist/mapping/loaders/tiny-loom.d.ts +2 -0
  70. package/dist/mapping/loaders/tiny-loom.js +73 -0
  71. package/dist/mapping/loaders/tiny-maven.d.ts +2 -0
  72. package/dist/mapping/loaders/tiny-maven.js +104 -0
  73. package/dist/mapping/loaders/types.d.ts +14 -0
  74. package/dist/mapping/loaders/types.js +2 -0
  75. package/dist/mapping/lookup.d.ts +52 -0
  76. package/dist/mapping/lookup.js +496 -0
  77. package/dist/mapping/parsers/normalize.d.ts +10 -0
  78. package/dist/mapping/parsers/normalize.js +52 -0
  79. package/dist/mapping/parsers/proguard.d.ts +20 -0
  80. package/dist/mapping/parsers/proguard.js +138 -0
  81. package/dist/mapping/parsers/symbol-records.d.ts +27 -0
  82. package/dist/mapping/parsers/symbol-records.js +216 -0
  83. package/dist/mapping/parsers/tiny.d.ts +9 -0
  84. package/dist/mapping/parsers/tiny.js +96 -0
  85. package/dist/mapping/types.d.ts +147 -0
  86. package/dist/mapping/types.js +2 -0
  87. package/dist/mapping-pipeline-service.d.ts +10 -1
  88. package/dist/mapping-pipeline-service.js +16 -3
  89. package/dist/mapping-service.d.ts +15 -144
  90. package/dist/mapping-service.js +179 -1119
  91. package/dist/mixin/access-validators.d.ts +9 -0
  92. package/dist/mixin/access-validators.js +257 -0
  93. package/dist/mixin/annotation-validators.d.ts +5 -0
  94. package/dist/mixin/annotation-validators.js +162 -0
  95. package/dist/mixin/helpers.d.ts +28 -0
  96. package/dist/mixin/helpers.js +315 -0
  97. package/dist/mixin/parsed-validator.d.ts +8 -0
  98. package/dist/mixin/parsed-validator.js +337 -0
  99. package/dist/mixin/types.d.ts +208 -0
  100. package/dist/mixin/types.js +28 -0
  101. package/dist/mixin-validator.d.ts +9 -201
  102. package/dist/mixin-validator.js +8 -1005
  103. package/dist/observability.d.ts +18 -1
  104. package/dist/observability.js +44 -1
  105. package/dist/response-utils.d.ts +44 -10
  106. package/dist/response-utils.js +131 -17
  107. package/dist/source/access-validate.d.ts +4 -0
  108. package/dist/source/access-validate.js +254 -0
  109. package/dist/source/artifact-resolver.d.ts +110 -0
  110. package/dist/source/artifact-resolver.js +1174 -0
  111. package/dist/source/cache-metrics.d.ts +26 -0
  112. package/dist/source/cache-metrics.js +172 -0
  113. package/dist/source/class-source/members-builder.d.ts +34 -0
  114. package/dist/source/class-source/members-builder.js +46 -0
  115. package/dist/source/class-source/snippet-builder.d.ts +19 -0
  116. package/dist/source/class-source/snippet-builder.js +46 -0
  117. package/dist/source/class-source-helpers.d.ts +34 -0
  118. package/dist/source/class-source-helpers.js +140 -0
  119. package/dist/source/class-source.d.ts +42 -0
  120. package/dist/source/class-source.js +883 -0
  121. package/dist/source/descriptor-utils.d.ts +6 -0
  122. package/dist/source/descriptor-utils.js +37 -0
  123. package/dist/source/file-access.d.ts +4 -0
  124. package/dist/source/file-access.js +102 -0
  125. package/dist/source/indexer.d.ts +82 -0
  126. package/dist/source/indexer.js +505 -0
  127. package/dist/source/lifecycle/diff-utils.d.ts +9 -0
  128. package/dist/source/lifecycle/diff-utils.js +107 -0
  129. package/dist/source/lifecycle/diff.d.ts +2 -0
  130. package/dist/source/lifecycle/diff.js +265 -0
  131. package/dist/source/lifecycle/mapping-helpers.d.ts +22 -0
  132. package/dist/source/lifecycle/mapping-helpers.js +327 -0
  133. package/dist/source/lifecycle/runtime-check.d.ts +2 -0
  134. package/dist/source/lifecycle/runtime-check.js +142 -0
  135. package/dist/source/lifecycle/trace.d.ts +2 -0
  136. package/dist/source/lifecycle/trace.js +231 -0
  137. package/dist/source/lifecycle.d.ts +4 -0
  138. package/dist/source/lifecycle.js +5 -0
  139. package/dist/source/search.d.ts +51 -0
  140. package/dist/source/search.js +676 -0
  141. package/dist/source/shared-utils.d.ts +6 -0
  142. package/dist/source/shared-utils.js +55 -0
  143. package/dist/source/state.d.ts +21 -0
  144. package/dist/source/state.js +19 -0
  145. package/dist/source/symbol-resolver.d.ts +3 -0
  146. package/dist/source/symbol-resolver.js +212 -0
  147. package/dist/source/validate-mixin/pipeline/mapping-health.d.ts +3 -0
  148. package/dist/source/validate-mixin/pipeline/mapping-health.js +41 -0
  149. package/dist/source/validate-mixin/pipeline/parse.d.ts +2 -0
  150. package/dist/source/validate-mixin/pipeline/parse.js +10 -0
  151. package/dist/source/validate-mixin/pipeline/resolve.d.ts +3 -0
  152. package/dist/source/validate-mixin/pipeline/resolve.js +78 -0
  153. package/dist/source/validate-mixin/pipeline/target-lookup.d.ts +6 -0
  154. package/dist/source/validate-mixin/pipeline/target-lookup.js +260 -0
  155. package/dist/source/validate-mixin/pipeline-context.d.ts +72 -0
  156. package/dist/source/validate-mixin/pipeline-context.js +93 -0
  157. package/dist/source/validate-mixin.d.ts +22 -0
  158. package/dist/source/validate-mixin.js +799 -0
  159. package/dist/source/workspace-target.d.ts +18 -0
  160. package/dist/source/workspace-target.js +305 -0
  161. package/dist/source-resolver.d.ts +9 -1
  162. package/dist/source-resolver.js +14 -6
  163. package/dist/source-service.d.ts +178 -105
  164. package/dist/source-service.js +72 -5312
  165. package/dist/stage-emitter.d.ts +13 -0
  166. package/dist/stage-emitter.js +30 -0
  167. package/dist/stdio-supervisor.d.ts +61 -0
  168. package/dist/stdio-supervisor.js +326 -9
  169. package/dist/storage/artifacts-repo.d.ts +4 -1
  170. package/dist/storage/artifacts-repo.js +33 -5
  171. package/dist/storage/files-repo.d.ts +0 -2
  172. package/dist/storage/files-repo.js +0 -11
  173. package/dist/storage/migrations.d.ts +1 -1
  174. package/dist/storage/migrations.js +10 -2
  175. package/dist/storage/schema.d.ts +2 -0
  176. package/dist/storage/schema.js +25 -0
  177. package/dist/tool-contract-manifest.d.ts +1 -1
  178. package/dist/tool-contract-manifest.js +23 -6
  179. package/dist/tool-guidance.d.ts +82 -0
  180. package/dist/tool-guidance.js +734 -0
  181. package/dist/tool-schema-registry.d.ts +16 -0
  182. package/dist/tool-schema-registry.js +37 -0
  183. package/dist/tool-schemas.d.ts +3518 -0
  184. package/dist/tool-schemas.js +813 -0
  185. package/dist/types.d.ts +39 -0
  186. package/dist/version-service.js +7 -6
  187. package/dist/workspace-context-cache.d.ts +32 -0
  188. package/dist/workspace-context-cache.js +66 -0
  189. package/dist/workspace-mapping-service.d.ts +16 -0
  190. package/dist/workspace-mapping-service.js +173 -1
  191. package/docs/README-ja.md +414 -0
  192. package/docs/examples.md +483 -0
  193. package/docs/tool-reference.md +459 -0
  194. package/package.json +5 -2
@@ -22,6 +22,7 @@ export interface RuntimeMetricSnapshot {
22
22
  get_file_duration_ms: MetricTimingSnapshot;
23
23
  list_files_duration_ms: MetricTimingSnapshot;
24
24
  decompile_duration_ms: MetricTimingSnapshot;
25
+ binary_remap_duration_ms: MetricTimingSnapshot;
25
26
  search_intent_symbol_duration_ms: MetricTimingSnapshot;
26
27
  search_intent_text_duration_ms: MetricTimingSnapshot;
27
28
  search_intent_path_duration_ms: MetricTimingSnapshot;
@@ -47,8 +48,13 @@ export interface RuntimeMetricSnapshot {
47
48
  cache_artifact_bytes_lru: CacheArtifactByteAccountingRow[];
48
49
  cache_hit_rate: number;
49
50
  repo_failover_count: number;
51
+ tool_call_counts: Record<string, number>;
52
+ tool_call_duration_ms: Record<string, MetricTimingSnapshot>;
53
+ mapping_resolution_cache_hits: number;
54
+ mapping_resolution_cache_misses: number;
55
+ mapping_resolution_cache_size: number;
50
56
  }
51
- type DurationMetricName = keyof Pick<RuntimeMetricSnapshot, "resolve_duration_ms" | "search_duration_ms" | "get_file_duration_ms" | "list_files_duration_ms" | "decompile_duration_ms" | "search_intent_symbol_duration_ms" | "search_intent_text_duration_ms" | "search_intent_path_duration_ms">;
57
+ type DurationMetricName = keyof Pick<RuntimeMetricSnapshot, "resolve_duration_ms" | "search_duration_ms" | "get_file_duration_ms" | "list_files_duration_ms" | "decompile_duration_ms" | "binary_remap_duration_ms" | "search_intent_symbol_duration_ms" | "search_intent_text_duration_ms" | "search_intent_path_duration_ms">;
52
58
  export declare class RuntimeMetrics {
53
59
  private readonly timings;
54
60
  private cacheHits;
@@ -74,6 +80,11 @@ export declare class RuntimeMetrics {
74
80
  private cacheEntries;
75
81
  private cacheTotalContentBytes;
76
82
  private cacheArtifactBytesLruRef;
83
+ private toolCallCounts;
84
+ private toolCallTimings;
85
+ private mappingResolutionCacheHits;
86
+ private mappingResolutionCacheMisses;
87
+ private mappingResolutionCacheSize;
77
88
  constructor();
78
89
  recordDuration(name: DurationMetricName, durationMs: number): void;
79
90
  recordArtifactCacheHit(): void;
@@ -97,6 +108,12 @@ export declare class RuntimeMetrics {
97
108
  setCacheEntries(entries: number): void;
98
109
  setCacheTotalContentBytes(totalBytes: number): void;
99
110
  setCacheArtifactByteAccountingRef(entries: ReadonlyArray<CacheArtifactByteAccountingRefRow>): void;
111
+ recordToolCall(tool: string, durationMs: number): void;
112
+ setMappingResolutionCacheStats(stats: {
113
+ hits: number;
114
+ misses: number;
115
+ size: number;
116
+ }): void;
100
117
  snapshot(): RuntimeMetricSnapshot;
101
118
  private toSnapshot;
102
119
  private resolveCacheHitRate;
@@ -32,6 +32,11 @@ export class RuntimeMetrics {
32
32
  cacheEntries = 0;
33
33
  cacheTotalContentBytes = 0;
34
34
  cacheArtifactBytesLruRef = [];
35
+ toolCallCounts = new Map();
36
+ toolCallTimings = new Map();
37
+ mappingResolutionCacheHits = 0;
38
+ mappingResolutionCacheMisses = 0;
39
+ mappingResolutionCacheSize = 0;
35
40
  constructor() {
36
41
  const names = [
37
42
  "resolve_duration_ms",
@@ -39,6 +44,7 @@ export class RuntimeMetrics {
39
44
  "get_file_duration_ms",
40
45
  "list_files_duration_ms",
41
46
  "decompile_duration_ms",
47
+ "binary_remap_duration_ms",
42
48
  "search_intent_symbol_duration_ms",
43
49
  "search_intent_text_duration_ms",
44
50
  "search_intent_path_duration_ms"
@@ -140,6 +146,27 @@ export class RuntimeMetrics {
140
146
  setCacheArtifactByteAccountingRef(entries) {
141
147
  this.cacheArtifactBytesLruRef = entries;
142
148
  }
149
+ recordToolCall(tool, durationMs) {
150
+ this.toolCallCounts.set(tool, (this.toolCallCounts.get(tool) ?? 0) + 1);
151
+ let timing = this.toolCallTimings.get(tool);
152
+ if (!timing) {
153
+ timing = { count: 0, totalMs: 0, lastMs: 0, samples: [] };
154
+ this.toolCallTimings.set(tool, timing);
155
+ }
156
+ const normalizedDuration = Math.max(0, Math.trunc(durationMs));
157
+ timing.count += 1;
158
+ timing.totalMs += normalizedDuration;
159
+ timing.lastMs = normalizedDuration;
160
+ timing.samples.push(normalizedDuration);
161
+ if (timing.samples.length > MAX_TIMING_SAMPLES) {
162
+ timing.samples.shift();
163
+ }
164
+ }
165
+ setMappingResolutionCacheStats(stats) {
166
+ this.mappingResolutionCacheHits = stats.hits;
167
+ this.mappingResolutionCacheMisses = stats.misses;
168
+ this.mappingResolutionCacheSize = stats.size;
169
+ }
143
170
  snapshot() {
144
171
  return {
145
172
  resolve_duration_ms: this.toSnapshot("resolve_duration_ms"),
@@ -147,6 +174,7 @@ export class RuntimeMetrics {
147
174
  get_file_duration_ms: this.toSnapshot("get_file_duration_ms"),
148
175
  list_files_duration_ms: this.toSnapshot("list_files_duration_ms"),
149
176
  decompile_duration_ms: this.toSnapshot("decompile_duration_ms"),
177
+ binary_remap_duration_ms: this.toSnapshot("binary_remap_duration_ms"),
150
178
  search_intent_symbol_duration_ms: this.toSnapshot("search_intent_symbol_duration_ms"),
151
179
  search_intent_text_duration_ms: this.toSnapshot("search_intent_text_duration_ms"),
152
180
  search_intent_path_duration_ms: this.toSnapshot("search_intent_path_duration_ms"),
@@ -175,7 +203,22 @@ export class RuntimeMetrics {
175
203
  updated_at: entry.updatedAt
176
204
  })),
177
205
  cache_hit_rate: this.resolveCacheHitRate(),
178
- repo_failover_count: this.repoFailoverCount
206
+ repo_failover_count: this.repoFailoverCount,
207
+ tool_call_counts: Object.fromEntries(this.toolCallCounts),
208
+ tool_call_duration_ms: Object.fromEntries([...this.toolCallTimings].map(([tool, timing]) => [
209
+ tool,
210
+ {
211
+ count: timing.count,
212
+ totalMs: timing.totalMs,
213
+ avgMs: timing.count > 0 ? timing.totalMs / timing.count : 0,
214
+ lastMs: timing.lastMs,
215
+ p95Ms: percentile(timing.samples, 95),
216
+ p99Ms: percentile(timing.samples, 99)
217
+ }
218
+ ])),
219
+ mapping_resolution_cache_hits: this.mappingResolutionCacheHits,
220
+ mapping_resolution_cache_misses: this.mappingResolutionCacheMisses,
221
+ mapping_resolution_cache_size: this.mappingResolutionCacheSize
179
222
  };
180
223
  }
181
224
  toSnapshot(name) {
@@ -7,29 +7,63 @@
7
7
  export declare const COMPACT_ENABLED_TOOL_NAMES: Set<string>;
8
8
  /** Mapping-oriented tools that get additional field projection via compactMappingResponse. */
9
9
  export declare const COMPACT_MAPPING_TOOL_NAMES: Set<string>;
10
+ /** Source-oriented tools (get-class-source) that get compactSourceResponse projection. */
11
+ export declare const COMPACT_SOURCE_TOOL_NAMES: Set<string>;
12
+ /** Member-listing tools (get-class-members) that get compactMembersResponse projection. */
13
+ export declare const COMPACT_MEMBERS_TOOL_NAMES: Set<string>;
14
+ /**
15
+ * Tools that only need the light artifactContents projection (search hits,
16
+ * file listing). The primary payload is already small; the projection just
17
+ * drops the artifact-level summary that callers rarely consume.
18
+ */
19
+ export declare const COMPACT_LIGHT_TOOL_NAMES: Set<string>;
10
20
  /**
11
21
  * Double-gated compact check: tool must be in the allowlist AND parsedInput.compact must be true.
12
22
  * Prevents activation on passthrough schemas where Zod doesn't strip unknown keys.
13
23
  */
14
24
  export declare function isCompactEnabled(tool: string, parsedInput: unknown): boolean;
25
+ /**
26
+ * Primary-payload keys that compact mode must preserve per tool, even when
27
+ * the value is an empty array (no hits, no files, no members). Without this
28
+ * the generic {@link compactResponse} path would strip `hits: []` / `items: []`
29
+ * from successful zero-result responses and callers could not distinguish
30
+ * "empty success" from "field missing".
31
+ *
32
+ * Only tools whose primary payload can legitimately be an empty array need
33
+ * an entry here. `get-class-source` returns `sourceText: string` which is
34
+ * never stripped by compactResponse.
35
+ */
36
+ export declare const TOOL_PRESERVE_PAYLOAD_KEYS: Record<string, ReadonlySet<string>>;
15
37
  /**
16
38
  * Shallow-strip empty values from a response object.
17
39
  * Only operates on the top level — nested structures are preserved as-is.
18
40
  * Non-plain objects (Date, Map, class instances) are never treated as empty.
41
+ *
42
+ * `preserveKeys` names keys whose values MUST survive the strip even if
43
+ * empty (used by tools whose primary payload is an array that can legitimately
44
+ * be empty — e.g. zero-hit search, empty file listing). `null` / `undefined`
45
+ * values are still dropped even for preserved keys, so absent optional
46
+ * payload fields do not leak through as explicit nulls.
19
47
  */
20
- export declare function compactResponse(obj: Record<string, unknown>): Record<string, unknown>;
48
+ export declare function compactResponse(obj: Record<string, unknown>, preserveKeys?: ReadonlySet<string>): Record<string, unknown>;
21
49
  /** resolve-artifact compact: omit debug/diagnostic fields. */
22
50
  export declare function compactArtifactResponse(obj: Record<string, unknown>): Record<string, unknown>;
51
+ /** get-class-source compact: drop provenance, artifactContents, qualityFlags. */
52
+ export declare function compactSourceResponse(obj: Record<string, unknown>): Record<string, unknown>;
53
+ /** get-class-members compact: drop provenance, artifactContents, qualityFlags, context. */
54
+ export declare function compactMembersResponse(obj: Record<string, unknown>): Record<string, unknown>;
55
+ /** Light compact projection: drop the artifactContents summary only. */
56
+ export declare function compactLightResponse(obj: Record<string, unknown>): Record<string, unknown>;
23
57
  /**
24
- * Mapping tool compact: omit candidates only when provably redundant.
58
+ * Mapping tool compact: project candidates for size reduction.
59
+ *
60
+ * Resolved-exact path: omit candidates entirely when provably redundant.
61
+ * All of: resolved===true, resolvedSymbol exists, single exact candidate, not truncated,
62
+ * confidence missing or 1.
25
63
  *
26
- * Candidates are omitted when ALL of:
27
- * 1. resolved === true
28
- * 2. resolvedSymbol exists
29
- * 3. candidates is an array of length 1
30
- * 4. candidateCount === 1
31
- * 5. candidatesTruncated is falsy
32
- * 6. candidates[0].matchKind === "exact"
33
- * 7. candidates[0].confidence is undefined or 1
64
+ * Unresolved/ambiguous path: keep top {@link UNRESOLVED_FULL_DETAIL_LIMIT} candidates with full
65
+ * metadata, slim the tail to {kind,symbol,owner,name,descriptor,confidence,matchKind}, and
66
+ * surface `candidatesTruncated:true` + `totalCandidateCount` so the caller knows what it's
67
+ * seeing.
34
68
  */
35
69
  export declare function compactMappingResponse(obj: Record<string, unknown>): Record<string, unknown>;
@@ -9,7 +9,11 @@ export const COMPACT_ENABLED_TOOL_NAMES = new Set([
9
9
  "find-mapping",
10
10
  "resolve-method-mapping-exact",
11
11
  "resolve-workspace-symbol",
12
- "check-symbol-exists"
12
+ "check-symbol-exists",
13
+ "get-class-source",
14
+ "get-class-members",
15
+ "search-class-source",
16
+ "list-artifact-files"
13
17
  ]);
14
18
  /** Mapping-oriented tools that get additional field projection via compactMappingResponse. */
15
19
  export const COMPACT_MAPPING_TOOL_NAMES = new Set([
@@ -18,6 +22,23 @@ export const COMPACT_MAPPING_TOOL_NAMES = new Set([
18
22
  "resolve-workspace-symbol",
19
23
  "check-symbol-exists"
20
24
  ]);
25
+ /** Source-oriented tools (get-class-source) that get compactSourceResponse projection. */
26
+ export const COMPACT_SOURCE_TOOL_NAMES = new Set([
27
+ "get-class-source"
28
+ ]);
29
+ /** Member-listing tools (get-class-members) that get compactMembersResponse projection. */
30
+ export const COMPACT_MEMBERS_TOOL_NAMES = new Set([
31
+ "get-class-members"
32
+ ]);
33
+ /**
34
+ * Tools that only need the light artifactContents projection (search hits,
35
+ * file listing). The primary payload is already small; the projection just
36
+ * drops the artifact-level summary that callers rarely consume.
37
+ */
38
+ export const COMPACT_LIGHT_TOOL_NAMES = new Set([
39
+ "search-class-source",
40
+ "list-artifact-files"
41
+ ]);
21
42
  /**
22
43
  * Double-gated compact check: tool must be in the allowlist AND parsedInput.compact must be true.
23
44
  * Prevents activation on passthrough schemas where Zod doesn't strip unknown keys.
@@ -39,18 +60,44 @@ function isPlainObject(v) {
39
60
  const proto = Object.getPrototypeOf(v);
40
61
  return proto === Object.prototype || proto === null;
41
62
  }
63
+ /**
64
+ * Primary-payload keys that compact mode must preserve per tool, even when
65
+ * the value is an empty array (no hits, no files, no members). Without this
66
+ * the generic {@link compactResponse} path would strip `hits: []` / `items: []`
67
+ * from successful zero-result responses and callers could not distinguish
68
+ * "empty success" from "field missing".
69
+ *
70
+ * Only tools whose primary payload can legitimately be an empty array need
71
+ * an entry here. `get-class-source` returns `sourceText: string` which is
72
+ * never stripped by compactResponse.
73
+ */
74
+ export const TOOL_PRESERVE_PAYLOAD_KEYS = {
75
+ "search-class-source": new Set(["hits"]),
76
+ "list-artifact-files": new Set(["items"]),
77
+ "get-class-members": new Set(["members", "counts", "decompiledFallback", "decompiledMemberCounts"])
78
+ };
42
79
  /**
43
80
  * Shallow-strip empty values from a response object.
44
81
  * Only operates on the top level — nested structures are preserved as-is.
45
82
  * Non-plain objects (Date, Map, class instances) are never treated as empty.
83
+ *
84
+ * `preserveKeys` names keys whose values MUST survive the strip even if
85
+ * empty (used by tools whose primary payload is an array that can legitimately
86
+ * be empty — e.g. zero-hit search, empty file listing). `null` / `undefined`
87
+ * values are still dropped even for preserved keys, so absent optional
88
+ * payload fields do not leak through as explicit nulls.
46
89
  */
47
- export function compactResponse(obj) {
90
+ export function compactResponse(obj, preserveKeys) {
48
91
  if (!isPlainObject(obj))
49
92
  return {};
50
93
  const result = {};
51
94
  for (const [key, value] of Object.entries(obj)) {
52
95
  if (value === null || value === undefined)
53
96
  continue;
97
+ if (preserveKeys?.has(key)) {
98
+ result[key] = value;
99
+ continue;
100
+ }
54
101
  if (Array.isArray(value) && value.length === 0)
55
102
  continue;
56
103
  if (isPlainObject(value) && Object.keys(value).length === 0)
@@ -59,6 +106,15 @@ export function compactResponse(obj) {
59
106
  }
60
107
  return result;
61
108
  }
109
+ function projectOmitKeys(obj, omit) {
110
+ const projected = {};
111
+ for (const [key, value] of Object.entries(obj)) {
112
+ if (omit.has(key))
113
+ continue;
114
+ projected[key] = value;
115
+ }
116
+ return projected;
117
+ }
62
118
  /** Fields to omit from resolve-artifact in compact mode. */
63
119
  const ARTIFACT_COMPACT_OMIT_KEYS = new Set([
64
120
  "provenance",
@@ -72,25 +128,69 @@ const ARTIFACT_COMPACT_OMIT_KEYS = new Set([
72
128
  ]);
73
129
  /** resolve-artifact compact: omit debug/diagnostic fields. */
74
130
  export function compactArtifactResponse(obj) {
75
- const projected = {};
76
- for (const [key, value] of Object.entries(obj)) {
77
- if (ARTIFACT_COMPACT_OMIT_KEYS.has(key))
78
- continue;
79
- projected[key] = value;
131
+ return projectOmitKeys(obj, ARTIFACT_COMPACT_OMIT_KEYS);
132
+ }
133
+ /** Fields to omit from get-class-source in compact mode. */
134
+ const SOURCE_COMPACT_OMIT_KEYS = new Set([
135
+ "provenance",
136
+ "artifactContents",
137
+ "qualityFlags"
138
+ ]);
139
+ /** get-class-source compact: drop provenance, artifactContents, qualityFlags. */
140
+ export function compactSourceResponse(obj) {
141
+ return projectOmitKeys(obj, SOURCE_COMPACT_OMIT_KEYS);
142
+ }
143
+ /** Fields to omit from get-class-members in compact mode. */
144
+ const MEMBERS_COMPACT_OMIT_KEYS = new Set([
145
+ "provenance",
146
+ "artifactContents",
147
+ "qualityFlags",
148
+ "context"
149
+ ]);
150
+ /** get-class-members compact: drop provenance, artifactContents, qualityFlags, context. */
151
+ export function compactMembersResponse(obj) {
152
+ return projectOmitKeys(obj, MEMBERS_COMPACT_OMIT_KEYS);
153
+ }
154
+ /** Fields to omit from search-class-source / list-artifact-files in compact mode. */
155
+ const LIGHT_COMPACT_OMIT_KEYS = new Set([
156
+ "artifactContents"
157
+ ]);
158
+ /** Light compact projection: drop the artifactContents summary only. */
159
+ export function compactLightResponse(obj) {
160
+ return projectOmitKeys(obj, LIGHT_COMPACT_OMIT_KEYS);
161
+ }
162
+ /** Max number of unresolved candidates that get full metadata in compact mode. */
163
+ const UNRESOLVED_FULL_DETAIL_LIMIT = 3;
164
+ /**
165
+ * Slim projection of a candidate: retains only identification + confidence fields.
166
+ *
167
+ * `kind` and `symbol` are part of the public `SymbolReference` / candidate contract that
168
+ * clients branch on and use as rendering keys, so they MUST survive the slim. The heavy
169
+ * fields removed here are the cycle-local diagnostic metadata (provenance, context,
170
+ * ambiguityReasons, warnings on the candidate, etc.) — not the identity fields.
171
+ */
172
+ function slimCandidate(candidate) {
173
+ if (!isPlainObject(candidate))
174
+ return candidate;
175
+ const picked = {};
176
+ for (const key of ["kind", "symbol", "owner", "name", "descriptor", "confidence", "matchKind"]) {
177
+ if (candidate[key] !== undefined) {
178
+ picked[key] = candidate[key];
179
+ }
80
180
  }
81
- return projected;
181
+ return picked;
82
182
  }
83
183
  /**
84
- * Mapping tool compact: omit candidates only when provably redundant.
184
+ * Mapping tool compact: project candidates for size reduction.
185
+ *
186
+ * Resolved-exact path: omit candidates entirely when provably redundant.
187
+ * All of: resolved===true, resolvedSymbol exists, single exact candidate, not truncated,
188
+ * confidence missing or 1.
85
189
  *
86
- * Candidates are omitted when ALL of:
87
- * 1. resolved === true
88
- * 2. resolvedSymbol exists
89
- * 3. candidates is an array of length 1
90
- * 4. candidateCount === 1
91
- * 5. candidatesTruncated is falsy
92
- * 6. candidates[0].matchKind === "exact"
93
- * 7. candidates[0].confidence is undefined or 1
190
+ * Unresolved/ambiguous path: keep top {@link UNRESOLVED_FULL_DETAIL_LIMIT} candidates with full
191
+ * metadata, slim the tail to {kind,symbol,owner,name,descriptor,confidence,matchKind}, and
192
+ * surface `candidatesTruncated:true` + `totalCandidateCount` so the caller knows what it's
193
+ * seeing.
94
194
  */
95
195
  export function compactMappingResponse(obj) {
96
196
  const projected = { ...obj };
@@ -106,8 +206,22 @@ export function compactMappingResponse(obj) {
106
206
  candidate.matchKind === "exact" &&
107
207
  (candidate.confidence === undefined || candidate.confidence === 1)) {
108
208
  delete projected.candidates;
209
+ return projected;
109
210
  }
110
211
  }
212
+ if (projected.resolved === false &&
213
+ Array.isArray(candidates) &&
214
+ candidates.length > UNRESOLVED_FULL_DETAIL_LIMIT) {
215
+ const head = candidates.slice(0, UNRESOLVED_FULL_DETAIL_LIMIT);
216
+ const tail = candidates.slice(UNRESOLVED_FULL_DETAIL_LIMIT).map(slimCandidate);
217
+ projected.candidates = [...head, ...tail];
218
+ // Tail slimming keeps the full candidate array; only metadata was dropped. Use a
219
+ // dedicated `candidateDetailsTruncated` signal so clients do not confuse it with the
220
+ // existing `candidatesTruncated` semantics ("more candidates exist than this response
221
+ // contains"). If the upstream already reported list-level truncation via
222
+ // `candidatesTruncated`, that value is preserved unchanged.
223
+ projected.candidateDetailsTruncated = true;
224
+ }
111
225
  return projected;
112
226
  }
113
227
  //# sourceMappingURL=response-utils.js.map
@@ -0,0 +1,4 @@
1
+ import type { SourceService } from "../source-service.js";
2
+ import type { ValidateAccessTransformerInput, ValidateAccessTransformerOutput, ValidateAccessWidenerInput, ValidateAccessWidenerOutput } from "../source-service.js";
3
+ export declare function validateAccessWidener(svc: SourceService, input: ValidateAccessWidenerInput): Promise<ValidateAccessWidenerOutput>;
4
+ export declare function validateAccessTransformer(svc: SourceService, input: ValidateAccessTransformerInput): Promise<ValidateAccessTransformerOutput>;
@@ -0,0 +1,254 @@
1
+ import { parseAccessTransformer } from "../access-transformer-parser.js";
2
+ import { parseAccessWidener } from "../access-widener-parser.js";
3
+ import { ERROR_CODES, createError } from "../errors.js";
4
+ import { validateParsedAccessTransformer, validateParsedAccessWidener } from "../mixin-validator.js";
5
+ import { normalizeMapping, normalizeOptionalString } from "./shared-utils.js";
6
+ function normalizeAccessWidenerNamespace(namespace) {
7
+ const normalized = namespace?.trim().toLowerCase();
8
+ if (!normalized) {
9
+ return undefined;
10
+ }
11
+ if (normalized === "named") {
12
+ return "yarn";
13
+ }
14
+ if (normalized === "obfuscated" ||
15
+ normalized === "mojang" ||
16
+ normalized === "intermediary" ||
17
+ normalized === "yarn") {
18
+ return normalized;
19
+ }
20
+ return undefined;
21
+ }
22
+ function isSourceMappingNamespace(namespace) {
23
+ return (namespace === "obfuscated" ||
24
+ namespace === "mojang" ||
25
+ namespace === "intermediary" ||
26
+ namespace === "yarn");
27
+ }
28
+ export async function validateAccessWidener(svc, input) {
29
+ const version = input.version.trim();
30
+ if (!version) {
31
+ throw createError({ code: ERROR_CODES.INVALID_INPUT, message: "version must be non-empty." });
32
+ }
33
+ const content = input.content;
34
+ if (!content.trim()) {
35
+ throw createError({ code: ERROR_CODES.INVALID_INPUT, message: "content must be non-empty." });
36
+ }
37
+ const warnings = [];
38
+ const parsed = parseAccessWidener(content);
39
+ const headerNamespaceRaw = normalizeOptionalString(parsed.namespace);
40
+ const overrideMapping = input.mapping ? normalizeMapping(input.mapping) : undefined;
41
+ const headerNamespace = normalizeAccessWidenerNamespace(headerNamespaceRaw);
42
+ if (!headerNamespace && headerNamespaceRaw && !overrideMapping) {
43
+ warnings.push(`Unsupported access widener namespace "${headerNamespaceRaw}". Assuming intermediary.`);
44
+ }
45
+ const awNamespace = overrideMapping ?? headerNamespace ?? "intermediary";
46
+ if (overrideMapping && headerNamespace && overrideMapping !== headerNamespace) {
47
+ warnings.push(`Using mapping override "${overrideMapping}" instead of header namespace "${headerNamespaceRaw}".`);
48
+ }
49
+ const runtimeAware = input.projectPath != null || input.scope != null || input.preferProjectVersion === true;
50
+ let resolvedVersion = version;
51
+ let jarPath;
52
+ let lookupMapping = "obfuscated";
53
+ let provenance;
54
+ if (runtimeAware) {
55
+ provenance = await svc.resolveAccessWidenerRuntimeArtifact({
56
+ version,
57
+ awNamespace,
58
+ projectPath: input.projectPath,
59
+ scope: input.scope,
60
+ preferProjectVersion: input.preferProjectVersion
61
+ });
62
+ resolvedVersion = provenance.version;
63
+ jarPath = provenance.jarPath;
64
+ lookupMapping = provenance.mappingApplied;
65
+ }
66
+ else {
67
+ ({ jarPath } = await svc.versionService.resolveVersionJar(version));
68
+ }
69
+ const needsLookupMapping = awNamespace !== lookupMapping;
70
+ // Collect unique class FQNs from entries
71
+ const classFqns = new Set();
72
+ for (const entry of parsed.entries) {
73
+ const fqn = entry.target.replace(/\//g, ".");
74
+ classFqns.add(fqn);
75
+ }
76
+ const membersByClass = new Map();
77
+ for (const fqn of classFqns) {
78
+ let lookupFqn = fqn;
79
+ if (needsLookupMapping) {
80
+ try {
81
+ const mapped = await svc.mappingService.findMapping({
82
+ version: resolvedVersion,
83
+ kind: "class",
84
+ name: fqn,
85
+ sourceMapping: awNamespace,
86
+ targetMapping: lookupMapping,
87
+ sourcePriority: input.sourcePriority,
88
+ projectPath: input.projectPath
89
+ });
90
+ if (mapped.resolved && mapped.resolvedSymbol) {
91
+ lookupFqn = mapped.resolvedSymbol.name;
92
+ }
93
+ else {
94
+ warnings.push(`Could not map class "${fqn}" from ${awNamespace} to ${lookupMapping}.`);
95
+ }
96
+ }
97
+ catch {
98
+ warnings.push(`Mapping lookup failed for class "${fqn}".`);
99
+ }
100
+ }
101
+ try {
102
+ const sig = await svc.explorerService.getSignature({
103
+ fqn: lookupFqn,
104
+ jarPath,
105
+ access: "all"
106
+ });
107
+ warnings.push(...sig.warnings);
108
+ let constructors = sig.constructors;
109
+ let methods = sig.methods;
110
+ let fields = sig.fields;
111
+ if (needsLookupMapping) {
112
+ const [ctorResult, methodResult, fieldResult] = await Promise.all([
113
+ svc.remapSignatureMembers(sig.constructors, "method", resolvedVersion, lookupMapping, awNamespace, input.sourcePriority, warnings, input.projectPath),
114
+ svc.remapSignatureMembers(sig.methods, "method", resolvedVersion, lookupMapping, awNamespace, input.sourcePriority, warnings, input.projectPath),
115
+ svc.remapSignatureMembers(sig.fields, "field", resolvedVersion, lookupMapping, awNamespace, input.sourcePriority, warnings, input.projectPath)
116
+ ]);
117
+ constructors = ctorResult.members;
118
+ methods = methodResult.members;
119
+ fields = fieldResult.members;
120
+ }
121
+ membersByClass.set(fqn, {
122
+ className: fqn,
123
+ classAccessFlags: sig.classAccessFlags,
124
+ constructors,
125
+ methods,
126
+ fields
127
+ });
128
+ }
129
+ catch {
130
+ warnings.push(`Could not load signature for class "${lookupFqn}".`);
131
+ }
132
+ }
133
+ const result = validateParsedAccessWidener(parsed, membersByClass, warnings, {
134
+ includeRuntimeEvidence: runtimeAware
135
+ });
136
+ if (provenance) {
137
+ result.provenance = provenance;
138
+ }
139
+ return result;
140
+ }
141
+ export async function validateAccessTransformer(svc, input) {
142
+ const version = input.version.trim();
143
+ if (!version) {
144
+ throw createError({ code: ERROR_CODES.INVALID_INPUT, message: "version must be non-empty." });
145
+ }
146
+ const content = input.content;
147
+ if (!content.trim()) {
148
+ throw createError({ code: ERROR_CODES.INVALID_INPUT, message: "content must be non-empty." });
149
+ }
150
+ const warnings = [];
151
+ const parsed = parseAccessTransformer(content);
152
+ const atNamespace = await svc.resolveAccessTransformerNamespace({
153
+ atNamespace: input.atNamespace,
154
+ projectPath: input.projectPath
155
+ });
156
+ const runtimeAware = input.projectPath != null || input.scope != null || input.preferProjectVersion === true;
157
+ let resolvedVersion = version;
158
+ let jarPath;
159
+ let lookupMapping = "obfuscated";
160
+ let provenance;
161
+ if (runtimeAware) {
162
+ provenance = await svc.resolveAccessTransformerRuntimeArtifact({
163
+ version,
164
+ atNamespace,
165
+ projectPath: input.projectPath,
166
+ scope: input.scope,
167
+ preferProjectVersion: input.preferProjectVersion
168
+ });
169
+ resolvedVersion = provenance.version;
170
+ jarPath = provenance.jarPath;
171
+ lookupMapping = provenance.mappingApplied;
172
+ }
173
+ else {
174
+ if (atNamespace === "srg") {
175
+ throw createError({
176
+ code: ERROR_CODES.INVALID_INPUT,
177
+ message: "atNamespace=srg requires projectPath and scope=loader so a Forge runtime jar can be resolved."
178
+ });
179
+ }
180
+ ({ jarPath } = await svc.versionService.resolveVersionJar(version));
181
+ }
182
+ const needsLookupMapping = atNamespace !== lookupMapping;
183
+ const classFqns = new Set(parsed.entries.map((entry) => entry.owner));
184
+ const membersByClass = new Map();
185
+ for (const fqn of classFqns) {
186
+ let lookupFqn = fqn;
187
+ if (needsLookupMapping) {
188
+ if (!isSourceMappingNamespace(atNamespace) || !isSourceMappingNamespace(lookupMapping)) {
189
+ warnings.push(`Could not map class "${fqn}" from ${atNamespace} to ${lookupMapping}.`);
190
+ }
191
+ else {
192
+ try {
193
+ const mapped = await svc.mappingService.findMapping({
194
+ version: resolvedVersion,
195
+ kind: "class",
196
+ name: fqn,
197
+ sourceMapping: atNamespace,
198
+ targetMapping: lookupMapping,
199
+ sourcePriority: input.sourcePriority,
200
+ projectPath: input.projectPath
201
+ });
202
+ if (mapped.resolved && mapped.resolvedSymbol) {
203
+ lookupFqn = mapped.resolvedSymbol.name;
204
+ }
205
+ else {
206
+ warnings.push(`Could not map class "${fqn}" from ${atNamespace} to ${lookupMapping}.`);
207
+ }
208
+ }
209
+ catch {
210
+ warnings.push(`Mapping lookup failed for class "${fqn}".`);
211
+ }
212
+ }
213
+ }
214
+ try {
215
+ const sig = await svc.explorerService.getSignature({
216
+ fqn: lookupFqn,
217
+ jarPath,
218
+ access: "all"
219
+ });
220
+ warnings.push(...sig.warnings);
221
+ let constructors = sig.constructors;
222
+ let methods = sig.methods;
223
+ let fields = sig.fields;
224
+ if (needsLookupMapping && isSourceMappingNamespace(atNamespace) && isSourceMappingNamespace(lookupMapping)) {
225
+ const [ctorResult, methodResult, fieldResult] = await Promise.all([
226
+ svc.remapSignatureMembers(sig.constructors, "method", resolvedVersion, lookupMapping, atNamespace, input.sourcePriority, warnings, input.projectPath),
227
+ svc.remapSignatureMembers(sig.methods, "method", resolvedVersion, lookupMapping, atNamespace, input.sourcePriority, warnings, input.projectPath),
228
+ svc.remapSignatureMembers(sig.fields, "field", resolvedVersion, lookupMapping, atNamespace, input.sourcePriority, warnings, input.projectPath)
229
+ ]);
230
+ constructors = ctorResult.members;
231
+ methods = methodResult.members;
232
+ fields = fieldResult.members;
233
+ }
234
+ membersByClass.set(fqn, {
235
+ className: fqn,
236
+ classAccessFlags: sig.classAccessFlags,
237
+ constructors,
238
+ methods,
239
+ fields
240
+ });
241
+ }
242
+ catch {
243
+ warnings.push(`Could not load signature for class "${lookupFqn}".`);
244
+ }
245
+ }
246
+ const result = validateParsedAccessTransformer(parsed, membersByClass, warnings, {
247
+ includeRuntimeEvidence: runtimeAware
248
+ });
249
+ if (provenance) {
250
+ result.provenance = provenance;
251
+ }
252
+ return result;
253
+ }
254
+ //# sourceMappingURL=access-validate.js.map