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:
cliffhall 2026-09-07 20:44:51 -04:00
commit d6b1e36e7e

View file

@ -352,7 +352,15 @@ export async function runMethod(
// not serve — "this server has no Skills support" and "no such skill"
// are different answers (Copilot).
assertSkillsSupported(inspectorClient, args.method);
const skill = await inspectorClient.getSkill(args.uri, args.metadata);
// The ENVELOPE, not the unwrapped entry. `getSkill` discards every other
// member the result carried — including the `ttlMs` / `cacheScope` that
// SEP-2640 explicitly leaves open — and a CLI whose contract is "print
// the result" must not drop what the server actually sent (Copilot).
const envelope = await inspectorClient.getSkillResult(
args.uri,
args.metadata,
);
const skill = envelope.skill;
if (args.verify) {
const reports = await verifySkills(
inspectorClient,
@ -368,11 +376,7 @@ export async function runMethod(
: { exitCode: EXIT_CODES.SKILL_NONCONFORMANT }),
};
}
// The `{ skill }` envelope is restored here because it is what the wire
// carries: `GetSkillResultSchema` unwraps it for callers that want the
// entry, and a CLI whose contract is "print the result" must not quietly
// reshape one.
result = { skill };
result = envelope;
} else if (args.method === "resources/directory/read") {
if (!args.uri) {
throw new Error(