@adhisang/minecraft-modding-mcp 7.0.0-rc.1 → 7.0.0-rc.3
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 +89 -1
- package/README.md +3 -1
- package/dist/artifact-identity.d.ts +130 -0
- package/dist/artifact-identity.js +142 -0
- package/dist/cache-registry.d.ts +10 -0
- package/dist/cache-registry.js +5 -1
- package/dist/config.js +2 -2
- package/dist/entry-tools/analyze-mod-service.d.ts +4 -4
- package/dist/entry-tools/analyze-symbol-service.d.ts +2 -2
- package/dist/entry-tools/batch-class-members-service.js +8 -0
- package/dist/entry-tools/entry-tool-schema.d.ts +2 -2
- package/dist/entry-tools/inspect-minecraft/handlers/class-members.js +21 -0
- package/dist/entry-tools/inspect-minecraft/internal.d.ts +4 -0
- package/dist/error-mapping.d.ts +46 -4
- package/dist/error-mapping.js +80 -11
- package/dist/index.js +3 -1
- package/dist/json-rpc-framing.d.ts +53 -1
- package/dist/json-rpc-framing.js +260 -36
- package/dist/mapping/loaders/tiny-loom-selection.js +10 -3
- package/dist/mapping/parsers/tiny.js +4 -0
- package/dist/mapping-service.js +24 -3
- package/dist/maven-resolver.d.ts +43 -0
- package/dist/maven-resolver.js +75 -4
- package/dist/maven-token.d.ts +65 -0
- package/dist/maven-token.js +120 -0
- package/dist/mcp-helpers.js +7 -3
- package/dist/minecraft-explorer-service.d.ts +13 -1
- package/dist/minecraft-explorer-service.js +201 -8
- package/dist/path-resolver.d.ts +13 -2
- package/dist/path-resolver.js +12 -1
- package/dist/repo-downloader.d.ts +119 -12
- package/dist/repo-downloader.js +687 -67
- package/dist/source/artifact-resolver.d.ts +85 -2
- package/dist/source/artifact-resolver.js +230 -29
- package/dist/source/class-source.d.ts +2 -2
- package/dist/source/class-source.js +77 -19
- package/dist/source/indexer.js +14 -0
- package/dist/source/nested-jars.d.ts +16 -1
- package/dist/source/nested-jars.js +66 -3
- package/dist/source/workspace-target.js +39 -20
- package/dist/source-jar-reader.d.ts +30 -2
- package/dist/source-jar-reader.js +39 -8
- package/dist/source-resolver.d.ts +48 -1
- package/dist/source-resolver.js +512 -94
- package/dist/source-service.d.ts +9 -0
- package/dist/stdio-supervisor.d.ts +221 -1
- package/dist/stdio-supervisor.js +728 -19
- package/dist/storage/artifacts-repo.d.ts +2 -0
- package/dist/storage/artifacts-repo.js +22 -0
- package/dist/storage/db.js +32 -10
- package/dist/tool-guidance.js +6 -3
- package/dist/tool-schemas.d.ts +63 -55
- package/dist/tool-schemas.js +4 -0
- package/dist/types.d.ts +18 -1
- package/dist/v1-parity-schemas.js +16 -0
- package/dist/version-diff-service.js +25 -4
- package/dist/version-service.js +23 -8
- package/dist/warning-details.js +3 -1
- package/dist/workspace-mapping-service.d.ts +8 -1
- package/dist/workspace-mapping-service.js +28 -19
- package/docs/tool-reference.md +52 -18
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,94 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [7.0.0-rc.3] - 2026-09-07
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **A jar named by path, a Minecraft version, or a workspace now takes its `artifactId` from a sha256 of the identified jar's bytes**, not from the jar's path plus its modification time and size. 7.0.0-rc.1 and 7.0.0-rc.2 moved the Maven-coordinate routes onto content hashing; this release moves the rest. `target.kind="version"` and `target.kind="workspace"` resolve through the same jar route, so the rule covers Minecraft's own runtime jar as well as a jar you name, and the lightweight artifact probe behind `validate-project task="project-summary"` follows it too. A `touch`, a cache eviction followed by a re-fetch of byte-identical bytes, or a restore from backup therefore no longer mints a new id or forces a re-decompile. Three bounds. The jar's symlink-resolved path is still part of the id, so two byte-identical jars at two different real paths remain two artifacts. Within one server process a file's digest is reused while its size, modification time, inode and inode-change time all still match, so on a filesystem that does not advance the inode-change time on a write, an in-place replacement of the same length whose modification time was restored can be served the earlier digest until that memo entry is evicted. And one pre-existing limit is untouched: when a Loom split-source pair is indexed together, the artifact's signature comes from the primary sources jar only, so a change confined to the companion jar is not represented in the id.
|
|
15
|
+
- **Upgrade cost of the identity change above.** Every artifact reached through `target.kind="jar"`, `"version"` or `"workspace"` — the Minecraft runtime included — gets a new `artifactId` on its first resolve after upgrading and re-indexes there, with a fresh decompile wherever the decompile branch is reached. There is deliberately no migration shim. An `artifactId` you already hold keeps resolving out of the index and does not re-index. The decompiled and remapped outputs stored under the previous ids are not reachable from the new ones, and nothing reclaims them as part of the upgrade; they stay visible to `manage-cache` under the `decompiled-source` and `binary-remap` cache kinds. Nothing bounds that first call end to end — see the upgrade note under Documentation — so on a large jar it can surface as a client-side request timeout rather than as a slow success.
|
|
16
|
+
- **Wire contract change.** The `artifactSignature` field returned by the `mc://artifact/{artifactId}` resource is now a bare 64-character sha256 hex digest, or that digest followed by `:decompile`. For an artifact reached through a jar, version or workspace target it was previously `<mtimeMs>:<size>`, so a client matching that shape — anything keyed to a digits-colon-digits pattern — stops matching. The new shape applies to artifacts resolved after upgrading; an artifact recorded before the upgrade and reached by an `artifactId` you still hold returns the signature it was stored with, so a client that keeps old ids must accept both shapes. Artifacts reached through a Maven coordinate already published a bare digest and are unchanged. This is the only place the field reaches a client: no tool response carries it, and `resolve-artifact` and `manage-cache` never did.
|
|
17
|
+
- **Behaviour change — two stdio header shapes that used to frame now end the session.** A `Content-Length` header block followed by an extra empty line is malformed, because the declared length does not count that line. Previously two of the four such shapes — an LF-terminated header line closed by a CRLF empty line, followed by either another CRLF or another LF — happened to skip past the extra line and framed correctly. All four now read alike: the body window opens one or two bytes early, by the length of the extra line's own terminator, so the JSON does not parse, and a body-parse failure is framing-fatal, so the session ends and the frames behind it are lost. The other two shapes already failed this way. One case still slips through in either direction: the window is shifted rather than mis-sized, so a declared length that happens to count enough trailing whitespace absorbs the shift and the frame is delivered normally.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Cancelling a queued request that reuses a running `validate-project`'s JSON-RPC id no longer lets other requests run on the worker alongside that `validate-project`. A `notifications/cancelled` for such an id matched the queued entry and, on the id match alone, released the dispatch barrier the running `validate-project` was holding, so other work was admitted beside it. Cancellation now releases the barrier only for a queued request that raised it itself; 7.0.0-rc.2's rule that a request cannot release a barrier it does not hold now covers cancellation too. Nothing could be stranded by this: the barrier was wrongly released, never wrongly left standing.
|
|
22
|
+
- Two identity bugs on symlinked paths are fixed. The artifact probe behind `validate-project task="project-summary"` and `resolve-artifact` now agree on the `artifactId` of a runtime jar reached through a symlinked directory. The probe composed its id from the path as handed to it while `resolve-artifact` resolved the symlink first, so `tasks["minecraft.artifact.resolved"].artifactId` and `resolve-artifact` reported two different ids for one jar; both now resolve the path first, and the probe's id changes for such jars. Separately, when a jar named by path carries no Java sources and its `<basename>-sources.jar` sibling supplies them, and that sibling is itself a symlink, its id now follows the real path. The `sourceJarPath` the response publishes is unchanged.
|
|
23
|
+
- A stdio peer that ends its `Content-Length` header lines with a bare LF but closes the block with a CRLF empty line can now be read at all. That shape matched neither of the two terminator patterns the reader looked for, so the block was never recognized: the reader accumulated until it hit the 8 KiB header ceiling and then ended the session, losing that request and every request behind it. A header block now ends at its first empty line whichever way the lines are terminated, so all four combinations frame, and a `\r\n\r\n` sequence inside a JSON body — legal whitespace — can no longer be mistaken for the terminator. This removes that stall, not the framing-fatal class as a whole: two header shapes that used to frame by accident now fail, which is the behaviour change recorded under Changed.
|
|
24
|
+
- Several ways a request could end up with no reply, or with two, are closed in the stdio supervisor. A request rejected because the queue was full now lowers the `validate-project` dispatch barrier it had raised, instead of leaving it standing with nothing able to lower it and nothing behind it able to dispatch. A fault while handling a worker's response, a fault inside the admission recovery itself, and a dispatch that fails while the queue drains are each now answered with a JSON-RPC `-32603` on the request's own id — previously the first two escaped into the frame reader, which reported a parse error and dropped the frame, and the third stranded the request and parked everything behind it; because only `validate-project` arms a deadline, any other request lost that way waited on its id for the life of the session. A missing pending entry is no longer read as proof the client was answered: when a fault interrupts the supervisor between dropping its own record of the request and handing the reply to the client, the id is still answered; a plain failed write of a reply is still only logged. A rollback that itself fails no longer lets a second terminal reply go out for one id. An in-flight `initialize` whose answer is lost to a fault now restarts the handshake against a replacement worker, where before it could wait indefinitely. And an error object whose own `toString` throws can no longer slip past these guards.
|
|
25
|
+
- Two limits on the repairs above, stated because a user can reach them. When the supervisor's cleanup after such a fault cannot prove the request was removed, it reports and stands down rather than risk two replies for one id; only `validate-project` carries a deadline of its own, so any other request left in that state waits for the worker to exit. And the record that discards a worker's late answer for an already-settled id holds at most 1024 ids at a time — a pre-existing cap this release does not change — so beyond that the oldest is evicted and a very old worker's late answer for an evicted id would pass through.
|
|
26
|
+
|
|
27
|
+
### Performance
|
|
28
|
+
|
|
29
|
+
- A repository's definitive rejection of a URL — 403, 404 or 410 — is now remembered, so resolving the same target again does not re-issue the requests every configured repository already refused. Previously nothing about a failed download was recorded: only bytes land in the download cache, so a coordinate whose sources jar does not exist paid the full repository sweep on every resolve, and a caller retrying after a timeout paid the identical sweep again. The bounds are part of the feature. The records live in memory only and are lost on restart. Each expires after about five minutes. At most 1024 records are kept at once; past that, expired records are dropped first and then the oldest, so a record can be lost before its five minutes are up. Only those three statuses are eligible: a 5xx, a 429, a 401, a request timeout and a network error are never remembered and are always retried. A `-SNAPSHOT` coordinate is never recorded — it is the one version shape this server treats as mutable — since its absence can be a publish in progress. And the records are kept per cache directory, so two configurations pointing at different cache directories never inherit each other's verdicts.
|
|
30
|
+
|
|
31
|
+
### Documentation
|
|
32
|
+
|
|
33
|
+
- **Correction to `docs/tool-reference.md` as shipped in 7.0.0-rc.2.** That release corrected the "Inspecting a Fabric / loader dependency like vanilla" target-shape table's claim that dependency members always come back with `qualityFlags: ["dependency-mapping-unverified"]`, but left the identical claim standing in the prose above it. The prose now names both branches: for a non-obfuscated mapping request, a source-backed jar that can honor it returns `mappingApplied` equal to the request and `qualityFlags` including `"source-backed"`; every other dependency jar answering that same request returns `mappingApplied: "obfuscated"` and `qualityFlags` including `"dependency-mapping-unverified"`, with names taken verbatim from the jar's compiled names. A request that asks for `"obfuscated"` outright never reaches that branch: it passes through as `mappingApplied: "obfuscated"` with `"source-backed"` or `"decompiled"`, never with `"dependency-mapping-unverified"`.
|
|
34
|
+
- `docs/tool-reference.md`'s "which requests count as tool-chosen" list in the error-classification section named `get-class-members`, `batch-class-members`, and `inspect-minecraft`'s `class-members` task but omitted `verify-mixin-target`, which reads its target's members from bytecode under the same rule and carries the same per-site `issueOrigin` override. The list now names all four.
|
|
35
|
+
- **Correction to `docs/tool-reference.md` as shipped in 7.0.0-rc.2.** The environment-variable section documented byte-derived jar identity only for jars downloaded through `MCP_SOURCE_REPOS`. It now also records that jars served from `MCP_LOCAL_M2` and the Gradle dependency cache are identified from their bytes the same way whenever reached through a Maven coordinate. The section now also documents the byte-derived rule for jar-path, version and workspace targets, which this release introduces (see Changed).
|
|
36
|
+
- **Correction to `docs/tool-reference.md` as shipped in 7.0.0-rc.2.** The legacy-era exceptions bullet claimed the advertised `inputSchema` bytes are "identical to the pre-migration snapshots" without qualification. The wording now says what the guarantee covers: the bytes `tools/list` advertises for legacy-era tools come from frozen pre-migration contracts, and those contracts are deliberately extended when a tool gains a parameter in a later release — as `projectPath` was added to four tools in 7.0.0-rc.2 itself. The parity guarantee was never broken; only its description was imprecise about what it promises.
|
|
37
|
+
- **The 7.0.0-rc.2 section was corrected in place.** New practice: a false statement in a released section is now amended where readers find it, not merely annotated from a later section. Two claims in its Maven-coordinate hardening entry were wrong. First, "naming the offending segment in `details.component`, including when a mandatory segment is blank": no `details` object reaches a client, and the message names the segment only for a character-rule rejection — a blank segment or a wrong-shaped coordinate names none — so it now reads "whose message names the offending segment when a segment breaks the character rule"; its `dependency`-route `fieldErrors` half is untouched. Second, "no write location was ever reachable, since every cache and artifact write derives its filename from a sha256": that reason is false server-wide — the mapping caches name files from a version string — so only the reason changed, to "because nothing on these routes writes to a coordinate-derived path"; the conclusion stands — those version-derived paths are written only after a successful fetch from a fixed upstream, which a traversal-bearing version cannot reach. Both sentences carry a trailing `(Corrected 2026-09-05; see [Unreleased].)`; the record they point to was written under `[Unreleased]` and now lives in this entry. The published 7.0.0-rc.2 npm tarball ships the changelog as it was released and cannot be amended, so a reader of the copy installed from npm still sees the original wording.
|
|
38
|
+
- **The 7.0.0-rc.0 section was corrected in place.** Its "Ending the session no longer leaves a worker process behind" entry said the signal path and the supervisor-fault path both "exit non-zero once the worker group is collected or a bounded watchdog expires". That is false for the signal path: `SIGHUP` runs the ordinary cooperative shutdown and the process exits with code 0 once the detached worker group is collected, exactly as when stdin closes; the non-zero code belongs to the uncaught-supervisor-fault path, which is how a launcher tells a crash from a clean stop. That entry now states the two exit codes separately and carries a trailing `(Corrected 2026-09-05; see [Unreleased].)`. Correcting it also falsified the closing clause of 7.0.0-rc.2's "Correction to 7.0.0-rc.0." note — "the published 7.0.0-rc.0 entry is left as it stands" — so that clause now records the rc.0 entry as corrected in place and carries the same marker. The published 7.0.0-rc.0 and 7.0.0-rc.2 npm tarballs ship their changelogs as released and cannot be amended, so a reader of either installed copy still sees the original wording.
|
|
39
|
+
- **Upgrade note for 7.0.0-rc.2.** That release said each already-cached local dependency re-indexes once on its first resolve after upgrading, but only as a cost. It can be slow enough to matter: a consumer measured a 95.5-second first `get-class-members` call. Nothing bounds that first call end to end: individual stages carry their own budgets, but no deadline covers the call as a whole, and only `validate-project` has one. Stage progress is emitted by `validate-project` and `validate-mixin` alone, as the custom `$/stageUpdate` notification rather than `notifications/progress`, so it cannot reset a client's timeout even where it is emitted. Whether it returns or is cut off rests on the client's own request timeout, which this server neither sets nor observes. What a retry saves: the artifact index is SQLite and survives the timeout and a restart, so a retry addressed at the `artifactId` skips the re-index. A retry addressed at the same `target` skips the re-index too, and now also skips the repository sweep, but only within the bounds of the remembered rejections under Performance — in-process, about five minutes, definitive rejections only — and the sweep is paid again after a restart or once that window closes. The jar-identity change in this release adds one re-index, and where the decompile branch is reached a fresh decompile, to that same unbounded first call — for the Minecraft runtime as well as local dependencies. The 95.5-second measurement itself is unexplained.
|
|
40
|
+
- `docs/tool-reference.md` now describes artifact identity as it works. Its environment-variable section states the byte-derived rule for jar-path, version and workspace targets alongside the coordinate routes, names which file each jar-route branch hashes, and records both bounds: the symlink-resolved path is still part of the id, and a file's digest is memoized within a process against its size, modification time, inode and inode-change time. The `mc://artifact/{artifactId}` row states the shape of the `artifactSignature` it returns. The `validate-project` task table and `README.md` both record that the artifact probe reads the runtime jar's bytes to derive the `artifactId` it reports.
|
|
41
|
+
- `docs/tool-reference.md` now documents the remembered definitive rejections in its repository section, with the bounds on them: the eligible statuses, the roughly five-minute expiry, the `-SNAPSHOT` exclusion, the per-cache-directory keying, and the fact that the records are lost on restart.
|
|
42
|
+
- `docs/tool-reference.md`'s Framing section now says where a `Content-Length` header block ends, that all four terminator combinations delimit one, and what an extra empty line after the terminator costs — including that the resulting body-parse failure is framing-fatal, and that a declared length counting enough trailing whitespace can absorb the shift instead. Its synthetic-terminal-response section now names the supervisor's internal-fault `-32603` replies alongside the queue-overflow, worker-restart, timeout and startup-failure shapes, and records the one case in which the supervisor deliberately sends nothing at all.
|
|
43
|
+
|
|
44
|
+
## [7.0.0-rc.2] - 2026-09-04
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
|
|
48
|
+
- Artifact downloads are now capped at 512 MiB per transfer, configurable with `MCP_MAX_DOWNLOAD_BYTES`. The ceiling is checked against the declared `Content-Length` before the body is read and again against the bytes actually received, so a chunked or mis-declared response is caught too. A breach fails with `ERR_LIMIT_EXCEEDED`, is not retried, and its partial file is removed on a best-effort basis; a cached copy is never served in its place. When every repository trips the cap, the final `ERR_REPO_FETCH_FAILED` says so: `hints` tells you to raise `MCP_MAX_DOWNLOAD_BYTES`, and `context.repoFailureCode` is `"ERR_LIMIT_EXCEEDED"`. Previously only a 15-second timeout bounded a transfer, so a fast link serving a very large body could fill the disk. The cap is per transfer and is unrelated to total cache size.
|
|
49
|
+
- Nested-jar extraction is now capped at 64 MiB per inner jar, configurable with `MCP_MAX_NESTED_JAR_ENTRY_BYTES`. The ceiling is checked against the entry's declared uncompressed size before extraction and against the bytes actually streamed. A breach fails that one entry with `ERR_LIMIT_EXCEEDED`; the shell jar's other inner jars still resolve. Previously inner jars were read whole into memory with no size check, so a highly compressible entry could expand far beyond the archive carrying it.
|
|
50
|
+
- The `nestedJars` error field is now bounded in bytes as well as entry count, and says when it has been shortened. Entries longer than 256 bytes are dropped individually, the list stops at 8 KiB in total, and `nestedJarsTruncated: true` accompanies a shortened list wherever one is emitted. Previously a truncated inventory was indistinguishable from a complete one.
|
|
51
|
+
- `resolve-artifact` now reports the `binary-jar-no-classes` quality flag for a binary jar that opens cleanly but contains no `.class` entry. Such jars are legitimate — `net.fabricmc:yarn` and `intermediary` `v2` carry only `mappings/mappings.tiny`, and a resource-only Fabric mod carries only `fabric.mod.json` and assets — so the flag describes the artifact and never refuses it. It is set for `coordinate`, `jar` and `version` targets alike. Known limit: a class-free jar that is not a Jar-in-Jar shell still fails during indexing with `ERR_DECOMPILER_FAILED` before any response exists, so the flag does not reach a caller in exactly that case.
|
|
52
|
+
|
|
53
|
+
### Changed
|
|
54
|
+
|
|
55
|
+
- **Wire contract change.** `context.jarHash` is renamed to `context.jarSignature` on `get-class-members` and `batch-class-members`. The value never was a hash of the jar's bytes: it is a signature over the jar's resolved path, modification time and size, useful for spotting that a jar changed between two responses but not as a content identity or cache key. The value and its derivation are unchanged. Callers reading `context.jarHash` must read `context.jarSignature` instead.
|
|
56
|
+
- **Wire contract change.** An artifact named only by an opaque handle no longer draws the blame for carrying no binary jar. `get-class-members`, `batch-class-members` and `inspect-minecraft`'s `class-members` task now apply one rule: a caller-named jar target (`target: { kind: "jar", ... }`) is the only shape that reports `issueOrigin: "code_issue"`; every other shape, an `artifactId` or `resolved-id` handle from an earlier resolve included, reports `"tool_issue"`. Previously `get-class-members` reported `"code_issue"` for `target: { kind: "artifact", artifactId }`, even though a handle tells its holder nothing about whether the artifact has a binary jar and `resolve-artifact` does not accept one. `retryClass` is unchanged and still `"input"` here. The error now points at `manage-cache action="inspect"` with `selector: { artifactId }` and `include: ["cacheEntries"]`, which reports the artifact's stored jar as `meta.binaryJarPath` inside `cacheEntries`. Clients that branch on `issueOrigin` should read it per response rather than assume a fixed value per error code.
|
|
57
|
+
- **A dependency's `artifactId` is now derived from the jar's bytes when the jar is served from `~/.m2` or the Gradle dependency cache**, not from its modification time and size. This applies to the routes that reach a jar through a Maven coordinate, `target.kind="dependency"` and `target.kind="coordinate"`; a jar named directly by path (`target.kind="jar"`) is unchanged and still takes its identity from that path with the file's modification time and size. 7.0.0-rc.1's content-derived `artifactId` covered only the remote-download path, so on the local path a `touch`, a cache eviction and re-fetch of identical bytes, or a filesystem restore still minted a new `artifactId`. Each already-cached local dependency gets a new `artifactId` and re-indexes once on its first resolve after upgrading, including a fresh decompile for binary-only dependencies.
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
|
|
61
|
+
- `search-class-source`, `get-artifact-file`, `list-artifact-files` and `index-artifact` now accept a top-level `projectPath` parameter, matching `find-class`, and advertise it in their `tools/list` `inputSchema`. Previously a `target.kind="workspace"` or unversioned `target.kind="dependency"` call to these four tools was rejected with an error asking for `projectPath`, but their schemas had no such field, so the error's own advice could not be followed.
|
|
62
|
+
- A dependency resolve no longer warns that its requested mapping "is not enforced" when a source-backed jar actually supplied it. The warning used to fire whenever a non-obfuscated mapping was requested for a dependency, contradicting the same response's `mappingApplied` and `qualityFlags` when the mapping had genuinely succeeded. It now fires only when the binary remap was actually skipped, that is, when `qualityFlags` includes `dependency-mapping-unverified`.
|
|
63
|
+
- A dependency's own coordinate version is no longer read as a signal about Minecraft's obfuscation state. A version that parses as a modern Minecraft version — any `26.x` or later, as `org.jetbrains:annotations:26.0.2` does — made a `mapping: "mojang"` request on a binary-only dependency return `mappingApplied: "mojang"` with no quality flag and no warning, claiming a remap that was never performed. Such a dependency now reports `mappingApplied: "obfuscated"` with the `dependency-mapping-unverified` flag and warning, on `resolve-artifact`, `get-class-source` and `get-class-members` alike. The obfuscation check now applies only to targets that genuinely name a Minecraft version — a `target.kind="version"`, or a `coordinate` target naming the Minecraft runtime itself such as `net.minecraft:client:26.1`; a `target.kind="dependency"` request is never treated as one — so vanilla resolution is unaffected.
|
|
64
|
+
- `get-class-members` and `batch-class-members` now report `context.minecraftVersion: "unknown"` for a dependency artifact instead of a plausible-but-wrong value. The version used to be guessed from the jar's path, which for a jar served out of the Gradle cache yielded the cache-layout constant `2.1` and under `~/.m2` the dependency's own release number, and a fallback then substituted the dependency's coordinate version. The workspace's Minecraft version is not substituted either: `"unknown"` reports what the tool genuinely knows. Vanilla artifacts are unaffected.
|
|
65
|
+
- `get-class-members`, `batch-class-members`, `find-class` and `get-class-source` now recognize a dependency artifact however it was reached. Previously only a `target.kind="dependency"` request marked the artifact as a dependency; the same jar reached by its Maven coordinate or reused by its `artifactId` was treated as vanilla, so `context.minecraftVersion` again reported the Gradle cache-layout constant `2.1` or the dependency's own release number, and a class-not-found miss drew a false hint to retry with `mapping="mojang"`. A dependency named by a Maven coordinate is now detected on every route, and `net.minecraft` alone no longer marks an artifact as Minecraft: the artifact name is read too, so `net.minecraft:launchwrapper:1.12` is a dependency, as is a group that merely ends in `net.minecraft` or an artifact whose name merely contains `minecraft-client`. A jar named by bare path (`target.kind="jar"`) under the Gradle dependency cache or the local Maven repository, wherever `MCP_LOCAL_M2` puts it, reports `"unknown"` unless the part of the path below the store's root names the Minecraft runtime; directories above the store are never consulted. Minecraft's own jars are unaffected. Remaining limit: a third-party jar whose own file name starts with `minecraft-`, such as `minecraft-client-helper`, still reports its own release number when named by a bare jar path; reach it with a `dependency` or `coordinate` target instead.
|
|
66
|
+
- `batch-class-members` and `inspect-minecraft`'s `class-members` task no longer blame the caller when the artifact they themselves resolved from the request's target carries no binary jar. Both collapse the request to a single artifact before reading members, and the failure was reported as `issueOrigin: "code_issue"` — once per entry on a batch — pointing at an input that could not have chosen otherwise. It is now `"tool_issue"`, matching `get-class-members`. A caller who named a jar themselves (`target: { kind: "jar", ... }`) still gets `"code_issue"`. This closes the gap 7.0.0-rc.1 recorded as outstanding for `batch-class-members`.
|
|
67
|
+
- A binary jar that `resolve-artifact` reports is now also persisted when the resolve was served from the warm cache. Previously that path skipped the write, so a binary companion that appeared in `~/.m2` or the Gradle cache after the artifact was first recorded, or the mojang-remapped jar reconciled for a `binary-remap:obf->mojang` artifact, reached the `resolve-artifact` response but not the stored artifact, and `get-class-members` on the returned `artifactId` then failed with `ERR_CONTEXT_UNRESOLVED` saying the artifact has no binary jar. Affected artifacts repair themselves on their next resolve; no re-index or cache clear is needed. A resolve that reaches no jar never clears a previously stored one.
|
|
68
|
+
- Cached artifact bytes that a concurrent resolve or cache prune deleted are now re-downloaded instead of failing the call with a raw `ENOENT`. Both a cache hit and a `304` revalidation treat vanished bytes as a cache miss and transfer again — unconditionally in the 304 case, carrying no validator from any source, caller-supplied `requestHeaders` included. A 304 revalidation no longer overwrites a fresher freshness record that a concurrent resolve left beside replaced `-SNAPSHOT` bytes: that record's digest, `etag` and `lastModified` are reported as they stand and no extra transfer is made. A cached entry that is present but unreadable — a permission error, a directory where a jar should be — is now reported instead of being treated as a miss, which had put that URL on the network on every call while hiding the actionable error. When that failed every repository, the final `ERR_REPO_FETCH_FAILED` names the offending cache path and its errno (`EACCES`, `EISDIR`, `EPERM`, `EIO`) in `hints` and says to remove or repair it.
|
|
69
|
+
- Evicting a poisoned download no longer destroys a concurrent resolve's good jar, and an eviction that cannot delete no longer leaves an entry the cache re-adopts forever. A response that is not a readable archive is dropped from the download cache, but the deletion named only the path, so when another resolve of the same URL had meanwhile finished a real jar into the same slot, that jar was deleted on the strength of a check run against different bytes. The eviction now compares the digest of the body it is rejecting with the identity record beside the file and leaves the file alone when they differ; this narrows the window rather than closing it, since another process can still replace the file between the comparison and the deletion. An entry no record can identify is still deleted, so a cache poisoned by an earlier version still heals itself. Separately, when the deletion itself fails — a read-only cache directory, say — the file is now truncated to zero bytes, which the cache treats exactly like a missing one, so the next transfer replaces it; previously whatever survived was served back and rejected again on every later resolve.
|
|
70
|
+
- `Retry-After` is now honoured in the RFC 9110 HTTP-date form. That form used to parse as `NaN` and fall through to a roughly 200-millisecond backoff, hammering a server that had just asked for a long pause. The numeric form is now read strictly, so `Retry-After: 12abc` is malformed rather than 12 seconds. Both forms stay capped at 30 seconds, a date already in the past behaves like `Retry-After: 0`, and only the space and horizontal tab RFC 9110 permits are stripped: a value padded with a newline, a form feed or a non-breaking space stays malformed instead of being repaired into a pause. The delta is measured against the local clock rather than the response's `Date` header.
|
|
71
|
+
- A caller-supplied Maven coordinate can no longer reach a jar outside the configured local repository. Every segment of `group:artifact:version[:classifier]` becomes a path component, and none was validated: an `artifactId` or `version` carrying `../` walked out of the repository root, and a `groupId` of `.`, `..` or `.a` landed at the filesystem root. Each segment is now trimmed and checked against one rule — 1 to 200 characters from `[A-Za-z0-9._+-]`, no leading `.`, no `..` — with `version` and `classifier` additionally admitting a space, because `net.fabricmc:yarn:1.14 Pre-Release 1+build.10:v2` is a real coordinate. A rejected coordinate fails with `ERR_COORDINATE_PARSE_FAILED`, whose message names the offending segment when a segment breaks the character rule; `g : a : 1.0` now resolves instead of failing on its padding. The `dependency` target route applies the same rule to `target.group` and `target.name` before it probes the Gradle cache (its old blocklist admitted `group="D:"` with `name="."`, which listed a directory outside the cache root into `candidatesSeen`), trims `target.version`, and names `target.group` or `target.name` in its field error instead of just `target`. Scope: the reachable impact was reading a file that is both named `<artifact>-<version>[-classifier][-sources].jar` and openable as a zip; no write location was ever reachable, because nothing on these routes writes to a coordinate-derived path. (Corrected 2026-09-05; see [Unreleased].)
|
|
72
|
+
- One corrupt `-sources.jar` in `~/.m2` no longer aborts artifact resolution. A truncated or non-zip sources jar among the resolution candidates used to surface as a generic `ERR_ARTIFACT_RESOLUTION_FAILED`, with the Gradle cache, the remote sources repositories, the local-binary decompile branch and the remote binary fallbacks never tried. An unopenable candidate is now treated as "no sources here" and resolution moves on, as the binary side already did. A jar the caller named directly still reports the open failure rather than laundering it into "no sources".
|
|
73
|
+
- A sources jar found in `~/.m2` or the Gradle module cache now picks its binary companion by opening the candidates, and looks in both stores. Previously each store took the first companion that merely existed, so a truncated or half-copied jar in one cache shadowed a good copy in the other, and the unusable path was what got recorded on the artifact, so every later `get-class-members` on that `artifactId` went to the broken file. Candidates are now tried in preference order, the sources jar's own store first, and the first one that opens wins. A companion that opens but holds no `.class` entry carries the `binary-jar-no-classes` quality flag here too.
|
|
74
|
+
- `validate-access-widener` and `validate-access-transformer` no longer answer `ERR_CONTEXT_UNRESOLVED` because of a directory name above the Gradle cache. A runtime jar's loader was read from its whole path, so a Fabric workspace checked out under a folder called `forge`, `moddev-notes` or `srg-test` was taken for a Forge or NeoForge runtime and refused. The loader is now read only from the part of the path a build tool wrote — everything below the deepest dot-directory (`.gradle`, `.m2`), Gradle's `caches` root or a project `build` directory, or the file name alone when the path enters none of them. A NeoForge-patched jar inside a Loom cache is still reported as NeoForge.
|
|
75
|
+
- A stdio peer that sends a `Content-Length` header and then stops sending the body no longer silences the connection. 7.0.0-rc.0 closed this only for a declared length above the frame limit; a smaller declared length followed by no body still parked the reader forever, with every later request stuck behind it. After 30 seconds with no further bytes the session now ends with a diagnostic naming the declared and received byte counts. The budget measures silence, not total transfer time: every arriving byte re-arms it, so a slow or very large body is never cut off.
|
|
76
|
+
- A header block whose lines contradict each other — a second `Content-Length` line even when the first value was unusable, or a malformed line alongside a good one — is now rejected outright and ends the session. Previously the reader stopped at the first bad line and consumed only the header block, so the body bytes an earlier declaration had claimed were re-read as separate messages and the peer effectively chose where the frame ended. A block that never declares a usable length is still reported as an ordinary parse error and recovered from.
|
|
77
|
+
- Peers that frame `Content-Length` headers with bare LF line endings are no longer mis-read when the JSON body contains a `\r\n\r\n` pair, which is legal JSON whitespace. A CRLF terminator anywhere in the buffer used to win over an earlier LF one, cutting the header block inside the body and losing that message and the next. The first terminator now wins, whichever style it is.
|
|
78
|
+
- A fault while handling an already-decoded frame no longer changes how later replies are framed. Such a throw used to reset the detected framing mode, so every later response was silently downgraded to line framing while the faulting request went unanswered. The fault is now reported through the ordinary parse-error channel with the framing state left as it was, and it can no longer tear the session down.
|
|
79
|
+
- A running `validate-project` no longer loses its timeout when a client reuses its JSON-RPC id. The deadline was matched by id alone, so a queued duplicate with the same id was timed out in its place while the real `validate-project` kept running with no deadline and still holding the dispatch barrier; whenever the worker never answered, everything queued behind it waited forever. A deadline now belongs to the exact request it was armed for, and a request that does not hold the barrier cannot release it.
|
|
80
|
+
- An internal failure while handing a request to the MCP worker no longer leaves that request half-registered. The supervisor answers such a fault with a JSON-RPC `-32603` "failed to admit the request" error, but the entry it had already installed stayed live: for `validate-project` the abandoned deadline later fired a second reply for the same id, and the dispatch barrier stayed raised until it did. The `-32603` is now the only reply for that id — the entry, its deadline and any barrier it held are released before the error is written — and queued work resumes immediately. An `initialize` that faults at this point keeps its own handshake recovery and is not rolled back here.
|
|
81
|
+
- A `tools/call` whose `arguments` are nested thousands of levels deep — a few KB on the wire — is now answered instead of vanishing. The argument redaction that runs at admission had no depth bound, so such a frame overflowed the stack and the request got no reply on an id the client then waited on forever. The walk now stops at 32 levels and reports anything deeper as `"<max-depth>"` in the diagnostic copies (`meta.timeout.redactedToolArgs`, `meta.restart.redactedToolArgs`, marked by `redactedToolArgsModified`); no real tool schema nests near that deep. Any request the supervisor cannot admit, for any reason, is now answered with JSON-RPC `-32603` rather than dropped.
|
|
82
|
+
- A worker or JVM that writes one very long stderr line with no newline no longer makes the server's memory grow until that worker is next restarted. The pending line is capped at 64 KiB: the part already held is passed through as one truncated line with a `supervisor.worker_stderr_line_truncated` warning, the rest of that line is discarded, and normal line handling resumes at the next newline, so worker startup is still detected.
|
|
83
|
+
- `compare-versions` no longer reports every class as both added and removed when only one of the two versions has usable Mojang mappings. Each side used to be lifted independently, so a pair where one side stayed obfuscated compared two different namespaces and returned `unchanged: 0` beside two full class lists. The diff now falls back to the jars' own class names on both sides, the `obfuscated` namespace the result already reported, and the warning announcing that fallback lists `version` alongside `packageFilter` in its `affectedFields`. One case the fallback cannot repair: when the two versions straddle Minecraft's move to unobfuscated names and the obfuscated side's mappings cannot be loaded, the jars' own names are still two different schemes, so `warnings` now says outright that the added and removed lists are not meaningful and `unchanged` will be near zero.
|
|
84
|
+
- A mapping file that leaves a namespace column empty on a class row no longer files that class's fields and methods under the previous class. The previous name was left standing for that namespace, so every member row that followed was indexed under the wrong owner and a lookup could be answered with a member the owner does not declare. Such a row now clears the namespace instead.
|
|
85
|
+
- `MCP_LOOM_TINY_MAX_INDEX_ENTRIES` now accepts only plain ASCII digits. A value such as `1e9`, `2_000_000`, `10M` or `1.9` used to be read from its leading digits as 1, 2, 10 or 1, small enough to truncate every real Loom mapping load; such a value now falls back to the heap-derived budget. A plain integer such as `250000` is still honoured, and may still be deliberately small.
|
|
86
|
+
- `find-mapping` and `resolve-method-mapping-exact` now return the mapping loader's own warnings in `warnings` whenever a mapping was loaded, `mapping_unavailable` included, as `get-class-api-matrix` and `check-symbol-exists` already did. The notice that a Loom mapping index stopped at its entry budget — the one signal that the answer may be incomplete — was being dropped on exactly the two tools most likely to hit it, and a `mapping_unavailable` answer replaced those warnings with its own single "no mapping path is available" sentence; both now appear together. On a version with no Mojang mappings these two tools now also surface the pre-existing `does not expose client mappings URL` notice.
|
|
87
|
+
- `resolve-method-mapping-exact` now says when a `not_found` was caused by an `owner` with no package. An unqualified owner cannot be projected into the target namespace and can never match, but the response explained the miss only as an inherited or relocated member. The message now names the owner and asks for the fully-qualified one.
|
|
88
|
+
- A version-manifest or version-detail fetch that fails at the transport layer — DNS, a refused connection, a TLS handshake — now fails with `ERR_REPO_FETCH_FAILED` instead of `ERR_INTERNAL`, so a network fault no longer reads as a server bug. The underlying message travels in `details.cause`; the timeout mapping is unchanged.
|
|
89
|
+
- `MCP_SQLITE_CACHE_KB` and `MCP_SQLITE_MMAP_SIZE` now also tune the artifact index that `manage-cache` opens. It used to open the same database on the built-in defaults — 8000 KiB page cache, 256 MiB memory map — whatever the two variables said.
|
|
90
|
+
- A failure while rebuilding a corrupted artifact index now fails with `ERR_DB_FAILURE` and closes the half-opened database handle, instead of escaping as a raw filesystem error from a read-only directory or a full disk. `details.reason` is `"rebuild_failed"` and the underlying message is carried in the error message.
|
|
91
|
+
|
|
92
|
+
### Documentation
|
|
93
|
+
|
|
94
|
+
- `docs/tool-reference.md` no longer tells a caller to work around a parameter that now exists. Its "Essential Conventions" entry named `find-class` as the only flat-`artifactId` tool accepting a top-level `projectPath` and directed callers of the other four to resolve the artifact first, contradicting the "Common Pitfalls" entry in the same document. Both now state that all five tools accept a top-level `projectPath` for a target that cannot supply workspace context from its own fields; resolve-first stays documented in "Common Pitfalls" as the optional alternative it still is.
|
|
95
|
+
- `docs/tool-reference.md` now documents the `context` block `get-class-members` and `batch-class-members` return. `jarSignature` is described as a signature over the jar's resolved path, modification time and size — good for spotting that the jar behind two responses is no longer the same file in the same state, usable neither as a content identity nor as a cache key. `minecraftVersion`, `mappingType`, `mappingNamespace` and `generatedAt` are described alongside it, including that `minecraftVersion` is `"unknown"` for a dependency artifact and for a jar inside a dependency store whose store-relative path does not name the Minecraft runtime, while a jar outside any such store is read from the first version-shaped token in its path.
|
|
96
|
+
- **Correction to 7.0.0-rc.0.** That release's "Ending the session no longer leaves a worker process behind" entry says the signal path and the supervisor-fault path both "exit non-zero once the worker group is collected or a bounded watchdog expires". That is wrong about the signal path: `SIGHUP`, `SIGINT` and `SIGTERM` run the ordinary cooperative shutdown and the process exits with code 0 once the detached worker group is collected, exactly as when stdin closes. Only an uncaught supervisor fault (`supervisor.fatal`) and a client framing violation (`supervisor.client_framing_fatal`) set exit code 1 on a running session, which is what lets a launcher tell a crash from a clean stop. No code changed, and the 7.0.0-rc.0 entry has since been corrected in place. (Corrected 2026-09-05; see [Unreleased].)
|
|
97
|
+
|
|
10
98
|
## [7.0.0-rc.1] - 2026-08-28
|
|
11
99
|
|
|
12
100
|
### Added
|
|
@@ -86,7 +174,7 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2
|
|
|
86
174
|
- Tool responses keep their typed error envelopes when SQLite is unavailable: input validation still returns `ERR_INVALID_INPUT`, database-independent tools such as the NBT utilities still return results, and a metrics-recording failure no longer replaces a completed response.
|
|
87
175
|
- The server recovers from a crashed worker instead of going unresponsive. A worker that hit a fatal `uncaughtException` or `unhandledRejection` used to stay alive with recovery bypassed; it now exits so the supervisor can replace it. A restart blocked by stale process-tree cleanup is retried with capped backoff instead of waiting for shutdown.
|
|
88
176
|
- The server no longer leaves orphaned processes behind. A keep-alive timer kept the worker's event loop running after stdin closed, so any launcher that terminated the server without a cooperative shutdown left a resident node process — about 125 MB on the reference host — and each restart added another. The worker now stands down on stdin EOF, and also when the parent process it recorded at startup disappears. Replies still being written after a half-close are delivered in full. A host that immediately closes every child's stdin now backs off exponentially instead of respawning a worker ten times a second, while a genuine crash still gets a prompt replacement. Measured on the reference host over one full test run each way: 20 orphaned workers before the fix, 0 after.
|
|
89
|
-
- Ending the session no longer leaves a worker process behind. `SIGHUP` — what a terminating launcher or a vanishing session sends — was never handled, and an uncaught error in the supervisor went to node's default handler, so either one left the detached worker process group with nobody to collect it. Both now run the ordinary shutdown path,
|
|
177
|
+
- Ending the session no longer leaves a worker process behind. `SIGHUP` — what a terminating launcher or a vanishing session sends — was never handled, and an uncaught error in the supervisor went to node's default handler, so either one left the detached worker process group with nobody to collect it. Both now run the ordinary shutdown path and collect that group before the process leaves, and the exit code says which of the two happened: `SIGHUP` is an ordinary cooperative stop and exits with code 0, exactly as when stdin closes, while an uncaught supervisor fault reports a `supervisor.fatal` event and exits non-zero once the worker group is collected or a bounded watchdog expires — so a launcher can tell a crash from a clean stop. (Corrected 2026-09-05; see [Unreleased].)
|
|
90
178
|
- The published package no longer risks shipping internal design documents. `files` listed `docs/**/*.md`, and because an npm `files` allowlist overrides `.gitignore`, a publish from a working tree containing local design notes would have included them. `files` now names the three intended documents explicitly.
|
|
91
179
|
- The automated npm release workflow can no longer publish a prerelease under the `latest` dist-tag: it derives the dist-tag from the package version, so a SemVer prerelease publishes under `rc` and an install without an explicit tag never picks up a release candidate.
|
|
92
180
|
|
package/README.md
CHANGED
|
@@ -172,8 +172,10 @@ These notes cover high-frequency decisions during onboarding. For the full pitfa
|
|
|
172
172
|
- `analyze-mod` and `validate-project` require structured `subject` objects and canonical `include` groups; stale string-subject or domain-include payloads return `ERR_INVALID_INPUT` with a retryable `suggestedCall`.
|
|
173
173
|
- `validate-project task="project-summary"` propagates `preferProjectVersion=true` across discovered Mixin, Access Widener, and Access Transformer checks. If no version can be resolved from the request or `gradle.properties`, the summary returns recovery guidance instead of guessing.
|
|
174
174
|
- `validate-mixin` and `validate-project` keep `mapping-health` lightweight for `obfuscated` and `mojang` validation, avoiding full Tiny mapping graph loads unless `intermediary` or `yarn` namespaces are requested.
|
|
175
|
-
- `validate-project task="project-summary"` uses a lightweight artifact probe for `tasks["minecraft.artifact.resolved"]`; it does not decompile Minecraft or rebuild the source index just to report per-probe status. Set `VALIDATE_PROJECT_TASKS_OFF=1` to omit the additive `tasks` field.
|
|
175
|
+
- `validate-project task="project-summary"` uses a lightweight artifact probe for `tasks["minecraft.artifact.resolved"]`; it does not decompile Minecraft or rebuild the source index just to report per-probe status. It does read the runtime jar's bytes to derive the `artifactId` it reports, and repeat probes of the same jar in one process reuse that digest. Set `VALIDATE_PROJECT_TASKS_OFF=1` to omit the additive `tasks` field.
|
|
176
176
|
- `validate-project` has a supervisor-owned end-to-end deadline of 120 seconds, including queue time. Set `MCP_VALIDATE_PROJECT_TIMEOUT_MS` to an ASCII-decimal value from `10000` through `600000` to override it. A timeout returns `ERR_TOOL_TIMEOUT`; a running timeout restarts the isolated worker before queued calls resume, while a queue timeout leaves the current worker untouched.
|
|
177
|
+
- Artifact downloads are capped at 512 MiB (`536870912` bytes) each. Set `MCP_MAX_DOWNLOAD_BYTES` to an ASCII-decimal byte count to override it; values below 1 MiB are raised to 1 MiB, and anything else — non-numeric, malformed, or too large to be an exact integer — falls back to the default. The cap is checked twice: against `Content-Length` before any body is read (a header that is not a plain byte count is treated as absent rather than as a breach), and against the bytes actually received, so a chunked or mis-declared response is caught too. A download that exceeds it fails with `ERR_LIMIT_EXCEEDED` and is not retried; the partial file is deleted on a best-effort basis, so a file the server cannot remove may survive the refusal. A cached copy is never served in its place — a cap is a configuration verdict, not a passing outage, and standing a stale artifact in for it would hide the ceiling instead of reporting it. When every repository trips the cap, the terminal `ERR_REPO_FETCH_FAILED` reports it where a caller can actually read it: `hints` carries the sentence naming `MCP_MAX_DOWNLOAD_BYTES` and the byte count to clear, and `context.repoFailureCode` is `ERR_LIMIT_EXCEEDED`.
|
|
178
|
+
- Nested (Jar-in-Jar) entries are capped at 64 MiB (`67108864` bytes) of uncompressed data each. Set `MCP_MAX_NESTED_JAR_ENTRY_BYTES` to override it, on the same rules as the download cap: ASCII decimal digits, values below 1 MiB raised to 1 MiB, anything else falling back to the default. The size is checked against the entry's declared uncompressed size before the read starts and again against the bytes actually streamed. A breach fails that one inner jar with `ERR_LIMIT_EXCEEDED`; the shell jar's other inner jars still resolve.
|
|
177
179
|
- Queued calls resume only after the replacement worker completes initialization replay. If replacement startup or replay fails, queued tool calls terminate with `ERR_WORKER_RESTART` instead of waiting indefinitely. If unresolved process-tree cleanup fills the supervisor's two live-generation slots, new requests fail with the existing restart envelope and unavailable notifications are warning-dropped until cleanup or reconnect. On POSIX, an already-gone process group counts as cleaned up rather than leaving a stale cleanup token.
|
|
178
180
|
- If a workspace was built with `GRADLE_USER_HOME=/tmp/...` or another isolated Gradle home, pass that path as `gradleUserHome` so source, mapping, runtime, and project validation lookups use the same Loom cache instead of stale caches under the MCP process home.
|
|
179
181
|
- `manage-cache` reports corrupt Mojang binary-remap cache directories under `cacheKinds: ["binary-remap"]` with `status: "corrupt"`, and can delete them by `selector.artifactId` in preview/apply workflows.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { MappingVariant } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The single owner of artifact identity: what an artifactId is derived from,
|
|
4
|
+
* and how the derived signature is composed into the id.
|
|
5
|
+
*
|
|
6
|
+
* Five call sites produce a jar id - three in `src/source-resolver.ts` and two
|
|
7
|
+
* in `src/source/artifact-resolver.ts` - and every one of them reaches it
|
|
8
|
+
* through {@link jarArtifactIdentity}, so the derivation is written once. The
|
|
9
|
+
* coordinate cascade's five results are composed by {@link composeArtifactId},
|
|
10
|
+
* which is the only place either id layout appears.
|
|
11
|
+
*
|
|
12
|
+
* Both id spaces derive the same way: a sha256 of the bytes of the file the id
|
|
13
|
+
* is about, produced by {@link contentSignature}. Neither composer takes a bare
|
|
14
|
+
* `signature: string`, and the one signature type there is can only be built
|
|
15
|
+
* from a value tagged as a content digest, so nothing cheaper can reach the
|
|
16
|
+
* hash by a route nobody reviewed.
|
|
17
|
+
*/
|
|
18
|
+
/** A sha256 of the bytes of the file itself. */
|
|
19
|
+
export interface ContentDigestSignature {
|
|
20
|
+
readonly from: "content-digest";
|
|
21
|
+
readonly value: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function contentDigestSignature(value: string): ContentDigestSignature;
|
|
24
|
+
/**
|
|
25
|
+
* An id for a jar named by its path.
|
|
26
|
+
*
|
|
27
|
+
* The layout is `jar | <path> | <signature> | source`, plus `mojang-remapped`
|
|
28
|
+
* when that variant applies. The trailing `source` is a constant part of the
|
|
29
|
+
* layout with no producer that varies it; it is written out because the digest
|
|
30
|
+
* depends on every part.
|
|
31
|
+
*/
|
|
32
|
+
export interface JarArtifactIdSpec {
|
|
33
|
+
readonly space: "jar";
|
|
34
|
+
/** Symlink-resolved, so two names for one file cannot mint two ids. */
|
|
35
|
+
readonly jarPath: string;
|
|
36
|
+
/**
|
|
37
|
+
* A sha256 of the jar's bytes, the same kind of signature the coordinate
|
|
38
|
+
* space takes. Required rather than merely permitted: a producer cannot
|
|
39
|
+
* identify a jar by something cheaper without changing this type.
|
|
40
|
+
*/
|
|
41
|
+
readonly signature: ContentDigestSignature;
|
|
42
|
+
/**
|
|
43
|
+
* Appended to the signature after a `:` before hashing. The decompile route
|
|
44
|
+
* is the only user: the same jar read for its sources and the same jar handed
|
|
45
|
+
* to the decompiler are different artifacts and must not share an id.
|
|
46
|
+
*/
|
|
47
|
+
readonly signatureQualifier?: string;
|
|
48
|
+
readonly mappingVariant?: MappingVariant;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* An id for an artifact reached through a Maven coordinate.
|
|
52
|
+
*
|
|
53
|
+
* The layout is `coord | <coordinate> | <idSource> | <signature>`, plus
|
|
54
|
+
* `mojang-remapped` when that variant applies.
|
|
55
|
+
*
|
|
56
|
+
* The signature is a content digest of the bytes this cascade resolved, the
|
|
57
|
+
* same as the jar space's.
|
|
58
|
+
*/
|
|
59
|
+
export interface CoordinateArtifactIdSpec {
|
|
60
|
+
readonly space: "coordinate";
|
|
61
|
+
readonly coordinate: string;
|
|
62
|
+
/**
|
|
63
|
+
* The id space this resolution belongs to, so artifacts discovered along
|
|
64
|
+
* different paths cannot collide in the hash.
|
|
65
|
+
*/
|
|
66
|
+
readonly idSource: string;
|
|
67
|
+
readonly signature: ContentDigestSignature;
|
|
68
|
+
readonly mappingVariant?: MappingVariant;
|
|
69
|
+
}
|
|
70
|
+
export type ArtifactIdSpec = JarArtifactIdSpec | CoordinateArtifactIdSpec;
|
|
71
|
+
/**
|
|
72
|
+
* The qualifier the decompile route puts on a jar's signature. Shared so the
|
|
73
|
+
* full resolve and the lightweight probe cannot spell it differently and mint
|
|
74
|
+
* two ids for one decompiled jar.
|
|
75
|
+
*/
|
|
76
|
+
export declare const DECOMPILE_SIGNATURE_QUALIFIER = "decompile";
|
|
77
|
+
/** Compose an artifactId. The only place either id layout is written down. */
|
|
78
|
+
export declare function composeArtifactId(spec: ArtifactIdSpec): string;
|
|
79
|
+
export interface JarArtifactIdentity {
|
|
80
|
+
/** The symlink-resolved path the signature was read from and the id composed from. */
|
|
81
|
+
readonly resolvedPath: string;
|
|
82
|
+
/** The signature as published in `ResolvedSourceArtifact.artifactSignature`, qualifier included. */
|
|
83
|
+
readonly signature: string;
|
|
84
|
+
readonly artifactId: string;
|
|
85
|
+
}
|
|
86
|
+
export interface JarArtifactIdentityOptions {
|
|
87
|
+
readonly signatureQualifier?: string;
|
|
88
|
+
readonly mappingVariant?: MappingVariant;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Normalize, derive, compose - in that order, for every jar-route producer.
|
|
92
|
+
*
|
|
93
|
+
* Normalizing first is what makes one jar under two names one artifact: the
|
|
94
|
+
* path that enters the hash is the resolved one, never the one handed in.
|
|
95
|
+
* `tests/source-service/probe-resolve-id-agreement.test.ts` pins that the
|
|
96
|
+
* lightweight probe and a full resolve agree through a symlinked directory.
|
|
97
|
+
*
|
|
98
|
+
* The derivation is {@link contentSignature}, so a jar named by path is
|
|
99
|
+
* identified by what is inside it: moving its mtime without changing a byte
|
|
100
|
+
* leaves the id where it was, and replacing its bytes moves the id.
|
|
101
|
+
*
|
|
102
|
+
* Hashing reads the whole file, so every producer calls this on its own branch,
|
|
103
|
+
* on the one file that branch's id is about, rather than once before the branch
|
|
104
|
+
* is picked. The branch that adopts a sibling `-sources.jar` therefore hashes
|
|
105
|
+
* that sibling and not the binary beside it, and the branch that finds no
|
|
106
|
+
* sources and cannot decompile hashes nothing at all.
|
|
107
|
+
*/
|
|
108
|
+
export declare function jarArtifactIdentity(jarPath: string, options?: JarArtifactIdentityOptions): Promise<JarArtifactIdentity>;
|
|
109
|
+
/**
|
|
110
|
+
* The identity of a jar sitting on local disk: a sha256 of its bytes.
|
|
111
|
+
*
|
|
112
|
+
* This is the derivation behind every artifactId this module composes - the
|
|
113
|
+
* coordinate cascade's local legs call it directly, and the jar route reaches
|
|
114
|
+
* it through {@link jarArtifactIdentity}.
|
|
115
|
+
*
|
|
116
|
+
* `~/.m2` and the Gradle module cache move a file's mtime for reasons that have
|
|
117
|
+
* nothing to do with its contents - an eviction followed by a re-fetch of
|
|
118
|
+
* byte-identical bytes, a filesystem restore, a plain `touch`. An `mtimeMs:size`
|
|
119
|
+
* signature turns every one of those into a fresh artifactId and a fresh
|
|
120
|
+
* decompile, which is exactly the instability the download cache's
|
|
121
|
+
* content-addressed identity removed from the remote half of the coordinate
|
|
122
|
+
* cascade.
|
|
123
|
+
*
|
|
124
|
+
* Hashing is not free and that cascade is re-walked on every target-driven tool
|
|
125
|
+
* call, so the digest is memoized against the stat that produced it. The stat is
|
|
126
|
+
* taken *before* the digest on purpose: bytes replaced mid-hash are recorded
|
|
127
|
+
* against a stat they no longer have, so the entry is rejected on the next call
|
|
128
|
+
* and re-derived - a wasted hash, never a wrong identity.
|
|
129
|
+
*/
|
|
130
|
+
export declare function contentSignature(jarPath: string): Promise<ContentDigestSignature>;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { statSync } from "node:fs";
|
|
2
|
+
import { stableArtifactId } from "./config.js";
|
|
3
|
+
import { normalizeJarPath } from "./path-resolver.js";
|
|
4
|
+
import { digestFile } from "./repo-downloader.js";
|
|
5
|
+
export function contentDigestSignature(value) {
|
|
6
|
+
return { from: "content-digest", value };
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The qualifier the decompile route puts on a jar's signature. Shared so the
|
|
10
|
+
* full resolve and the lightweight probe cannot spell it differently and mint
|
|
11
|
+
* two ids for one decompiled jar.
|
|
12
|
+
*/
|
|
13
|
+
export const DECOMPILE_SIGNATURE_QUALIFIER = "decompile";
|
|
14
|
+
function qualifySignature(value, qualifier) {
|
|
15
|
+
return qualifier === undefined ? value : `${value}:${qualifier}`;
|
|
16
|
+
}
|
|
17
|
+
/** Compose an artifactId. The only place either id layout is written down. */
|
|
18
|
+
export function composeArtifactId(spec) {
|
|
19
|
+
const parts = spec.space === "jar"
|
|
20
|
+
// "jar" and "coord" are the tokens that enter the hash. They are not the
|
|
21
|
+
// `space` discriminant values, which only pick the layout.
|
|
22
|
+
? ["jar", spec.jarPath, qualifySignature(spec.signature.value, spec.signatureQualifier), "source"]
|
|
23
|
+
: ["coord", spec.coordinate, spec.idSource, spec.signature.value];
|
|
24
|
+
if ((spec.mappingVariant ?? "pass") === "mojang-remapped") {
|
|
25
|
+
parts.push("mojang-remapped");
|
|
26
|
+
}
|
|
27
|
+
return stableArtifactId(parts);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Normalize, derive, compose - in that order, for every jar-route producer.
|
|
31
|
+
*
|
|
32
|
+
* Normalizing first is what makes one jar under two names one artifact: the
|
|
33
|
+
* path that enters the hash is the resolved one, never the one handed in.
|
|
34
|
+
* `tests/source-service/probe-resolve-id-agreement.test.ts` pins that the
|
|
35
|
+
* lightweight probe and a full resolve agree through a symlinked directory.
|
|
36
|
+
*
|
|
37
|
+
* The derivation is {@link contentSignature}, so a jar named by path is
|
|
38
|
+
* identified by what is inside it: moving its mtime without changing a byte
|
|
39
|
+
* leaves the id where it was, and replacing its bytes moves the id.
|
|
40
|
+
*
|
|
41
|
+
* Hashing reads the whole file, so every producer calls this on its own branch,
|
|
42
|
+
* on the one file that branch's id is about, rather than once before the branch
|
|
43
|
+
* is picked. The branch that adopts a sibling `-sources.jar` therefore hashes
|
|
44
|
+
* that sibling and not the binary beside it, and the branch that finds no
|
|
45
|
+
* sources and cannot decompile hashes nothing at all.
|
|
46
|
+
*/
|
|
47
|
+
export async function jarArtifactIdentity(jarPath, options = {}) {
|
|
48
|
+
const resolvedPath = normalizeJarPath(jarPath);
|
|
49
|
+
const signature = await contentSignature(resolvedPath);
|
|
50
|
+
return {
|
|
51
|
+
resolvedPath,
|
|
52
|
+
signature: qualifySignature(signature.value, options.signatureQualifier),
|
|
53
|
+
artifactId: composeArtifactId({
|
|
54
|
+
space: "jar",
|
|
55
|
+
jarPath: resolvedPath,
|
|
56
|
+
signature,
|
|
57
|
+
signatureQualifier: options.signatureQualifier,
|
|
58
|
+
mappingVariant: options.mappingVariant
|
|
59
|
+
})
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Digests already derived from a local jar, each pinned to the stat that
|
|
64
|
+
* produced it.
|
|
65
|
+
*
|
|
66
|
+
* Keyed by the symlink-resolved path, so a jar reached through two names is
|
|
67
|
+
* hashed once. The entry is only ever *reused*, never trusted on its own: it is
|
|
68
|
+
* discarded unless size, mtimeMs, inode and ctimeMs all still match the file on
|
|
69
|
+
* disk. ctimeMs is in that key because it is the field a write moves that
|
|
70
|
+
* `utimes` cannot put back, so a same-size replacement carrying a restored
|
|
71
|
+
* mtime is re-hashed rather than served the digest of the bytes it replaced.
|
|
72
|
+
*
|
|
73
|
+
* Known limitation: that guarantee is only as strong as the platform's
|
|
74
|
+
* inode-change timestamp. Where a filesystem does not advance ctimeMs on a
|
|
75
|
+
* write, this key degrades to size + mtime + inode and a same-size replacement
|
|
76
|
+
* whose mtime was restored is served the recorded digest until the entry is
|
|
77
|
+
* evicted. Nothing here re-reads the bytes to check.
|
|
78
|
+
*
|
|
79
|
+
* Bounded, like the helper caches in `src/source/artifact-resolver.ts`: this map
|
|
80
|
+
* is module-level and lives as long as the process, and a long-running server
|
|
81
|
+
* walks the resolution cascade once per target-driven tool call, so an
|
|
82
|
+
* unbounded map grows with every distinct jar path the server has ever seen.
|
|
83
|
+
* Eviction costs at most one re-hash, which is exactly what a cache miss
|
|
84
|
+
* already costs.
|
|
85
|
+
*/
|
|
86
|
+
const contentSignatureCache = new Map();
|
|
87
|
+
const MAX_CONTENT_SIGNATURE_CACHE = 512;
|
|
88
|
+
/** Insert, dropping the oldest key first when the bound is reached. */
|
|
89
|
+
function rememberContentSignature(resolvedPath, entry) {
|
|
90
|
+
if (!contentSignatureCache.has(resolvedPath) && contentSignatureCache.size >= MAX_CONTENT_SIGNATURE_CACHE) {
|
|
91
|
+
const oldestKey = contentSignatureCache.keys().next().value;
|
|
92
|
+
if (oldestKey) {
|
|
93
|
+
contentSignatureCache.delete(oldestKey);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
contentSignatureCache.set(resolvedPath, entry);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The identity of a jar sitting on local disk: a sha256 of its bytes.
|
|
100
|
+
*
|
|
101
|
+
* This is the derivation behind every artifactId this module composes - the
|
|
102
|
+
* coordinate cascade's local legs call it directly, and the jar route reaches
|
|
103
|
+
* it through {@link jarArtifactIdentity}.
|
|
104
|
+
*
|
|
105
|
+
* `~/.m2` and the Gradle module cache move a file's mtime for reasons that have
|
|
106
|
+
* nothing to do with its contents - an eviction followed by a re-fetch of
|
|
107
|
+
* byte-identical bytes, a filesystem restore, a plain `touch`. An `mtimeMs:size`
|
|
108
|
+
* signature turns every one of those into a fresh artifactId and a fresh
|
|
109
|
+
* decompile, which is exactly the instability the download cache's
|
|
110
|
+
* content-addressed identity removed from the remote half of the coordinate
|
|
111
|
+
* cascade.
|
|
112
|
+
*
|
|
113
|
+
* Hashing is not free and that cascade is re-walked on every target-driven tool
|
|
114
|
+
* call, so the digest is memoized against the stat that produced it. The stat is
|
|
115
|
+
* taken *before* the digest on purpose: bytes replaced mid-hash are recorded
|
|
116
|
+
* against a stat they no longer have, so the entry is rejected on the next call
|
|
117
|
+
* and re-derived - a wasted hash, never a wrong identity.
|
|
118
|
+
*/
|
|
119
|
+
export async function contentSignature(jarPath) {
|
|
120
|
+
// The same normalization the jar route applies, kept so this path still
|
|
121
|
+
// refuses a vanished or non-jar file the way it always has.
|
|
122
|
+
const resolvedPath = normalizeJarPath(jarPath);
|
|
123
|
+
const stats = statSync(resolvedPath);
|
|
124
|
+
const cached = contentSignatureCache.get(resolvedPath);
|
|
125
|
+
if (cached &&
|
|
126
|
+
cached.mtimeMs === stats.mtimeMs &&
|
|
127
|
+
cached.size === stats.size &&
|
|
128
|
+
cached.ino === stats.ino &&
|
|
129
|
+
cached.ctimeMs === stats.ctimeMs) {
|
|
130
|
+
return contentDigestSignature(cached.sha256);
|
|
131
|
+
}
|
|
132
|
+
const { contentSha256 } = await digestFile(resolvedPath);
|
|
133
|
+
rememberContentSignature(resolvedPath, {
|
|
134
|
+
mtimeMs: stats.mtimeMs,
|
|
135
|
+
size: stats.size,
|
|
136
|
+
ino: stats.ino,
|
|
137
|
+
ctimeMs: stats.ctimeMs,
|
|
138
|
+
sha256: contentSha256
|
|
139
|
+
});
|
|
140
|
+
return contentDigestSignature(contentSha256);
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=artifact-identity.js.map
|
package/dist/cache-registry.d.ts
CHANGED
|
@@ -45,6 +45,16 @@ export declare function pathContainsVersion(path: string, version: string): bool
|
|
|
45
45
|
export type CacheRegistryConfig = {
|
|
46
46
|
cacheDir: string;
|
|
47
47
|
sqlitePath: string;
|
|
48
|
+
/**
|
|
49
|
+
* SQLite page-cache and memory-map tuning for the artifact index, as
|
|
50
|
+
* MCP_SQLITE_CACHE_KB and MCP_SQLITE_MMAP_SIZE resolve them. Optional because
|
|
51
|
+
* a caller with no opinion should get the same defaults `applyPragmas` gives
|
|
52
|
+
* every other reader of the index - but a caller that HAS one is the whole
|
|
53
|
+
* point: without these the registry opened the index on the built-in defaults,
|
|
54
|
+
* so the two variables tuned every consumer except manage-cache.
|
|
55
|
+
*/
|
|
56
|
+
sqliteCacheKb?: number;
|
|
57
|
+
sqliteMmapSize?: number;
|
|
48
58
|
pathRuntimeInfo?: PathRuntimeInfo;
|
|
49
59
|
workspaceContextCache?: WorkspaceContextCache;
|
|
50
60
|
};
|
package/dist/cache-registry.js
CHANGED
|
@@ -227,7 +227,11 @@ function openDb(config) {
|
|
|
227
227
|
if (!existsSync(config.sqlitePath)) {
|
|
228
228
|
return undefined;
|
|
229
229
|
}
|
|
230
|
-
return openDatabase(
|
|
230
|
+
return openDatabase({
|
|
231
|
+
sqlitePath: config.sqlitePath,
|
|
232
|
+
sqliteCacheKb: config.sqliteCacheKb,
|
|
233
|
+
sqliteMmapSize: config.sqliteMmapSize
|
|
234
|
+
}).db;
|
|
231
235
|
}
|
|
232
236
|
function candidatePathsForEntry(entry) {
|
|
233
237
|
const paths = new Set();
|
package/dist/config.js
CHANGED
|
@@ -229,8 +229,8 @@ export function buildArtifactAlias(input) {
|
|
|
229
229
|
else {
|
|
230
230
|
tokens.push("jar", aliasJarBase(input.value));
|
|
231
231
|
}
|
|
232
|
-
// mappingVariant is canonical — `
|
|
233
|
-
//
|
|
232
|
+
// mappingVariant is canonical — `composeArtifactId` in `src/artifact-identity.ts`
|
|
233
|
+
// bakes "mojang-remapped" into the artifactId, so the same artifactId always
|
|
234
234
|
// produces the same alias regardless of how many times resolveArtifact is
|
|
235
235
|
// called. The token here is purely a human-readable hint; the 12-char hash
|
|
236
236
|
// suffix would already separate remapped artifacts from pass-through ones.
|
|
@@ -3,8 +3,8 @@ import { z } from "zod";
|
|
|
3
3
|
export declare const analyzeModShape: {
|
|
4
4
|
task: z.ZodEnum<{
|
|
5
5
|
search: "search";
|
|
6
|
-
summary: "summary";
|
|
7
6
|
decompile: "decompile";
|
|
7
|
+
summary: "summary";
|
|
8
8
|
"class-source": "class-source";
|
|
9
9
|
members: "members";
|
|
10
10
|
remap: "remap";
|
|
@@ -22,8 +22,8 @@ export declare const analyzeModShape: {
|
|
|
22
22
|
class: "class";
|
|
23
23
|
field: "field";
|
|
24
24
|
method: "method";
|
|
25
|
-
all: "all";
|
|
26
25
|
content: "content";
|
|
26
|
+
all: "all";
|
|
27
27
|
}>>;
|
|
28
28
|
limit: z.ZodDefault<z.ZodNumber>;
|
|
29
29
|
includeFiles: z.ZodDefault<z.ZodBoolean>;
|
|
@@ -51,8 +51,8 @@ export declare const analyzeModShape: {
|
|
|
51
51
|
export declare const analyzeModSchema: z.ZodObject<{
|
|
52
52
|
task: z.ZodEnum<{
|
|
53
53
|
search: "search";
|
|
54
|
-
summary: "summary";
|
|
55
54
|
decompile: "decompile";
|
|
55
|
+
summary: "summary";
|
|
56
56
|
"class-source": "class-source";
|
|
57
57
|
members: "members";
|
|
58
58
|
remap: "remap";
|
|
@@ -70,8 +70,8 @@ export declare const analyzeModSchema: z.ZodObject<{
|
|
|
70
70
|
class: "class";
|
|
71
71
|
field: "field";
|
|
72
72
|
method: "method";
|
|
73
|
-
all: "all";
|
|
74
73
|
content: "content";
|
|
74
|
+
all: "all";
|
|
75
75
|
}>>;
|
|
76
76
|
limit: z.ZodDefault<z.ZodNumber>;
|
|
77
77
|
includeFiles: z.ZodDefault<z.ZodBoolean>;
|
|
@@ -2,8 +2,8 @@ import { z } from "zod";
|
|
|
2
2
|
import type { CheckSymbolExistsOutput, FindMappingOutput, GetClassApiMatrixOutput, ResolveMethodMappingExactOutput, ResolveWorkspaceSymbolOutput, TraceSymbolLifecycleOutput } from "../source-service.js";
|
|
3
3
|
export declare const analyzeSymbolShape: {
|
|
4
4
|
task: z.ZodEnum<{
|
|
5
|
-
map: "map";
|
|
6
5
|
workspace: "workspace";
|
|
6
|
+
map: "map";
|
|
7
7
|
exists: "exists";
|
|
8
8
|
"exact-map": "exact-map";
|
|
9
9
|
lifecycle: "lifecycle";
|
|
@@ -72,8 +72,8 @@ export declare const analyzeSymbolShape: {
|
|
|
72
72
|
};
|
|
73
73
|
export declare const analyzeSymbolSchema: z.ZodObject<{
|
|
74
74
|
task: z.ZodEnum<{
|
|
75
|
-
map: "map";
|
|
76
75
|
workspace: "workspace";
|
|
76
|
+
map: "map";
|
|
77
77
|
exists: "exists";
|
|
78
78
|
"exact-map": "exact-map";
|
|
79
79
|
lifecycle: "lifecycle";
|
|
@@ -48,6 +48,14 @@ export class BatchClassMembersService {
|
|
|
48
48
|
}
|
|
49
49
|
const raw = (await this.deps.getClassMembers({
|
|
50
50
|
artifactId: sharedArtifact.artifactId,
|
|
51
|
+
// This artifactId is OURS, not the caller's: the shared target was
|
|
52
|
+
// resolved above and every entry is dispatched by the result. Without
|
|
53
|
+
// saying so, get-class-members reads the bare presence of an
|
|
54
|
+
// artifactId as the caller having named the artifact, and reports a
|
|
55
|
+
// missing binary jar as their mistake - once per entry - though
|
|
56
|
+
// `target` here cannot name an artifact at all. Only a jar the caller
|
|
57
|
+
// named themselves is genuinely their choice.
|
|
58
|
+
artifactSelectedBy: input.target.kind === "jar" ? "caller" : "tool",
|
|
51
59
|
className: entry.className,
|
|
52
60
|
access: entry.access,
|
|
53
61
|
includeSynthetic: entry.includeSynthetic,
|
|
@@ -5,10 +5,10 @@ export declare const detailSchema: z.ZodEnum<{
|
|
|
5
5
|
full: "full";
|
|
6
6
|
}>;
|
|
7
7
|
export declare const includeGroupSchema: z.ZodEnum<{
|
|
8
|
-
entries: "entries";
|
|
9
8
|
workspace: "workspace";
|
|
10
|
-
|
|
9
|
+
entries: "entries";
|
|
11
10
|
source: "source";
|
|
11
|
+
candidates: "candidates";
|
|
12
12
|
warnings: "warnings";
|
|
13
13
|
registry: "registry";
|
|
14
14
|
preview: "preview";
|