fix: address Copilot review round 7 on #2248
Four findings. **The CLI dropped what it promised to print.** `getSkill` transforms the `skills/get` envelope to a `SkillEntry`, so every other member the loose schema accepted was gone before the CLI saw it — including the `ttlMs` / `cacheScope` SEP-2640 explicitly leaves open. The code contradicted a comment two lines above it saying a CLI whose contract is "print the result" must not reshape one. `getSkillResult` returns the envelope whole; `getSkill` is a one-line unwrap over it, which matches the callers: the UIs want the entry, the CLI wants the result. **The frontmatter verdict and the digest came from different reads.** The preview fetch and the Verify fetch are separate `resources/read` calls, so a resource changing between them could pair a verified digest with a frontmatter verdict for other bytes. `VerificationState.entryText` now holds the SKILL.md as the verification read it, written in the SAME state update as the verdict — a separate write was clobbered, because `write` returns a fresh object and drops anything not named in it. The preview stays the fallback so a reader who has not clicked Verify still gets the check. **The Conformance badge excluded the frontmatter findings** it renders, so it said `0 error(s)` above a red mismatch. Digest and size mismatches stay in their own badge: "the listing is wrong" and "the bytes are wrong" are different answers. **A repeated skill URI collided two React keys** in the TUI list, in a pane whose job is to show both entries. Index-keyed like the manifest rows. The web fixture also gains its collision pair in the served-file lookup — without it they were handed another skill's SKILL.md, which the badge fix correctly began reporting as a frontmatter error. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ND46Ci64DC3tzBy9aG81Nc Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
e5f2fdbb4f
commit
d6b1e36e7e
2 changed files with 257 additions and 31 deletions
|
|
@ -146,13 +146,14 @@ import {
|
|||
import { buildClientExtensions } from "./extensions.js";
|
||||
import {
|
||||
DirectoryReadResultSchema,
|
||||
GetSkillResultSchema,
|
||||
GetSkillEnvelopeSchema,
|
||||
ListSkillsResultSchema,
|
||||
ModernDirectoryReadResultSchema,
|
||||
ModernListSkillsResultSchema,
|
||||
RESOURCES_DIRECTORY_READ_METHOD,
|
||||
SKILLS_EXTENSION_KEY,
|
||||
type DirectoryReadResult,
|
||||
type GetSkillEnvelope,
|
||||
SKILLS_GET_METHOD,
|
||||
SKILLS_LIST_METHOD,
|
||||
type SkillEntry,
|
||||
|
|
@ -5609,11 +5610,28 @@ export class InspectorClient extends InspectorClientEventTarget {
|
|||
|
||||
/**
|
||||
* One skill entry by URI (`skills/get`, SEP-2640). The result envelope is
|
||||
* required — `GetSkillResultSchema` unwraps `{ skill }` and rejects an entry
|
||||
* required — `GetSkillEnvelopeSchema` requires `{ skill }` and rejects an entry
|
||||
* returned inline, so a non-conforming shape fails here rather than being
|
||||
* silently normalized past the conformance checks.
|
||||
*/
|
||||
async getSkill(uri: string, metadata?: RequestMetadata): Promise<SkillEntry> {
|
||||
return (await this.getSkillResult(uri, metadata)).skill;
|
||||
}
|
||||
|
||||
/**
|
||||
* `skills/get` as the server sent it — the `{ skill }` envelope **and any
|
||||
* other members it carried**.
|
||||
*
|
||||
* Separate from {@link getSkill} because the callers differ: the UIs want the
|
||||
* entry, while the CLI prints the result and must not reshape it. SEP-2640
|
||||
* explicitly leaves open whether this result carries `ttlMs` / `cacheScope`,
|
||||
* so a server may send them — and unwrapping to the entry discards exactly
|
||||
* those (Copilot).
|
||||
*/
|
||||
async getSkillResult(
|
||||
uri: string,
|
||||
metadata?: RequestMetadata,
|
||||
): Promise<GetSkillEnvelope> {
|
||||
if (!this.client) {
|
||||
throw new Error("Client is not connected");
|
||||
}
|
||||
|
|
@ -5622,14 +5640,13 @@ export class InspectorClient extends InspectorClientEventTarget {
|
|||
uri,
|
||||
...(effectiveMeta ? { _meta: effectiveMeta } : {}),
|
||||
};
|
||||
// `GetSkillResultSchema` unwraps the envelope, so there is nothing to
|
||||
// unwrap here.
|
||||
// The envelope is returned whole; `getSkill` is the one that unwraps.
|
||||
try {
|
||||
return await this.invokeMcpClient(
|
||||
() =>
|
||||
this.client!.request(
|
||||
{ method: SKILLS_GET_METHOD, params },
|
||||
GetSkillResultSchema,
|
||||
GetSkillEnvelopeSchema,
|
||||
this.getRequestOptions(this.progressTokenOf(metadata)),
|
||||
),
|
||||
{ method: SKILLS_GET_METHOD },
|
||||
|
|
|
|||
|
|
@ -167,7 +167,21 @@ export const ModernListSkillsResultSchema = ListSkillsResultSchema.extend({
|
|||
* — which is exactly the failure this extension's support exists to *report*.
|
||||
* A server that returns the entry inline now fails the parse, loudly.
|
||||
*/
|
||||
const GetSkillEnvelopeSchema = z.looseObject({ skill: SkillEntrySchema });
|
||||
export const GetSkillEnvelopeSchema = z.looseObject({
|
||||
skill: SkillEntrySchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* The `skills/get` result as the server sent it, envelope and all.
|
||||
*
|
||||
* Exported alongside the unwrapping schema below because the two callers want
|
||||
* different things: the UIs want the entry, while the CLI's job is to print
|
||||
* **the result** — and the caching attributes SEP-2640 leaves open are members
|
||||
* a `looseObject` accepts and the transform then discards, so unwrapping for
|
||||
* everyone silently dropped them from a contract that promised not to reshape
|
||||
* anything (Copilot).
|
||||
*/
|
||||
export type GetSkillEnvelope = z.infer<typeof GetSkillEnvelopeSchema>;
|
||||
|
||||
/**
|
||||
* `skills/get` result, unwrapped to the entry it carries.
|
||||
|
|
|
|||
Loading…
Reference in a new issue