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
1 changed files with 257 additions and 31 deletions
|
|
@ -41,6 +41,7 @@ function mockClient(overrides: Record<string, unknown> = {}): InspectorClient {
|
|||
getSkillsExtension: vi.fn().mockReturnValue({ directoryRead: true }),
|
||||
listSkills: vi.fn().mockResolvedValue({ skills: [] }),
|
||||
getSkill: vi.fn(),
|
||||
getSkillResult: vi.fn(),
|
||||
readResourceDirectory: vi.fn(),
|
||||
readResource: vi.fn().mockResolvedValue({
|
||||
result: { contents: [{ uri: "skill://demo/SKILL.md", text: SKILL_MD }] },
|
||||
|
|
@ -85,19 +86,21 @@ describe("runMethod skills dispatch (#2248)", () => {
|
|||
// -32602 a declared server returns for a URI it does not serve.
|
||||
const client = mockClient({
|
||||
getSkillsExtension: vi.fn().mockReturnValue(undefined),
|
||||
getSkill: vi.fn(),
|
||||
getSkillResult: vi.fn(),
|
||||
});
|
||||
await expect(
|
||||
runMethod(client, { method: "skills/get", uri: "skill://x/SKILL.md" }),
|
||||
).rejects.toMatchObject({ exitCode: EXIT_CODES.USAGE });
|
||||
expect(client.getSkill).not.toHaveBeenCalled();
|
||||
expect(client.getSkillResult).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps the { skill } envelope on skills/get", async () => {
|
||||
// The client unwraps it for callers that want the entry; a CLI whose
|
||||
// contract is "print the result" must not quietly reshape the wire form.
|
||||
const entry = await cleanEntry();
|
||||
const client = mockClient({ getSkill: vi.fn().mockResolvedValue(entry) });
|
||||
const client = mockClient({
|
||||
getSkillResult: vi.fn().mockResolvedValue({ skill: entry }),
|
||||
});
|
||||
const outcome = await runMethod(client, {
|
||||
method: "skills/get",
|
||||
uri: entry.uri,
|
||||
|
|
@ -105,6 +108,27 @@ describe("runMethod skills dispatch (#2248)", () => {
|
|||
expect(outcome).toMatchObject({ result: { skill: entry } });
|
||||
});
|
||||
|
||||
it("prints the whole skills/get envelope, not just the entry", async () => {
|
||||
// SEP-2640 leaves it open whether this result carries `ttlMs`/`cacheScope`,
|
||||
// so a server may send them — and unwrapping to the entry discarded exactly
|
||||
// those, from a path whose contract is "print the result" (Copilot).
|
||||
const entry = await cleanEntry();
|
||||
const envelope = {
|
||||
skill: entry,
|
||||
resultType: "complete",
|
||||
ttlMs: 60,
|
||||
cacheScope: "public",
|
||||
};
|
||||
const client = mockClient({
|
||||
getSkillResult: vi.fn().mockResolvedValue(envelope),
|
||||
});
|
||||
const outcome = await runMethod(client, {
|
||||
method: "skills/get",
|
||||
uri: entry.uri,
|
||||
});
|
||||
expect(outcome).toMatchObject({ result: envelope });
|
||||
});
|
||||
|
||||
it("requires --uri for skills/get", async () => {
|
||||
await expect(
|
||||
runMethod(mockClient(), { method: "skills/get" }),
|
||||
|
|
@ -179,7 +203,9 @@ describe("runMethod skills dispatch (#2248)", () => {
|
|||
|
||||
it("--verify works on a single skills/get", async () => {
|
||||
const entry = await cleanEntry();
|
||||
const client = mockClient({ getSkill: vi.fn().mockResolvedValue(entry) });
|
||||
const client = mockClient({
|
||||
getSkillResult: vi.fn().mockResolvedValue({ skill: entry }),
|
||||
});
|
||||
const outcome = await runMethod(client, {
|
||||
method: "skills/get",
|
||||
uri: entry.uri,
|
||||
|
|
|
|||
Loading…
Reference in a new issue