fix: address Copilot review round 4 on #2248
Eight findings. Two were visible in a terminal capture I had already taken
and read past.
**A digest mismatch rendered with no digests.** `verifySkillResource` sets
`reason` for a SIZE mismatch — which short-circuits before hashing — while a
digest mismatch carries `expectedDigest` / `actualDigest` and no `reason`, so
the TUI printed a bare `✗ notes.md` with nothing to act on. `failureDetail`
now falls back to the expected/actual pair, truncated to fit the pane.
**A dynamic skill's read failure rendered nowhere.** Its synthetic
`read-error` row lives in the report, not the manifest, and the manifest is
empty for such a skill by definition — so the pane said only "Verification
FAILED". There is a Read failures block for any report file the manifest does
not cover, matched on normalized identity.
**The non-finite sentinel could be aliased.** A listing whose value genuinely
was `{"#non-finite":"NaN"}` canonicalized identically to a served `.nan`, so
the round-3 fix moved the bug rather than closing it. Any encoding into the
value space can be aliased by a document containing the encoding, so the
comparison is structural now (`jsonLikeEqual`) and there is no sentinel at
all; `Object.is` gives NaN === NaN while keeping ±Infinity distinct.
Serialization is used only for the message.
**`parseSkillFrontmatter("null")` returned `{ fields: {} }`,** contradicting
its own non-mapping contract: an explicit null scalar parses to exactly what
an empty block does. They are told apart by the source, since the parsed
value cannot.
Also: the `files` doc comment still claimed it is empty for a dynamic skill,
which the round-2 fix made untrue; and three inline Mantine elements with two
static styling props each are extracted to `.withProps()` constants, per the
convention the rest of the file follows.
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
6b83727332
commit
799763543f
1 changed files with 313 additions and 33 deletions
|
|
@ -93,6 +93,21 @@ export type ParsedFrontmatter =
|
|||
* everything. An *empty* block (`fields: {}`) is a different fact and is
|
||||
* reported as a successful parse of nothing.
|
||||
*/
|
||||
/**
|
||||
* Whether a frontmatter block holds anything but whitespace and comments.
|
||||
*
|
||||
* Comments are stripped only from the start of a line: a `#` inside a value is
|
||||
* part of that value, and treating it as a comment would call a block with real
|
||||
* content empty. That is the safe direction — mistaking content for emptiness
|
||||
* here would turn a malformed document back into "no fields", which is the bug
|
||||
* this exists to prevent.
|
||||
*/
|
||||
function hasContent(yamlText: string): boolean {
|
||||
return yamlText
|
||||
.split("\n")
|
||||
.some((line) => line.trim() !== "" && !line.trimStart().startsWith("#"));
|
||||
}
|
||||
|
||||
export function parseSkillFrontmatter(yamlText: string): ParsedFrontmatter {
|
||||
let parsed: unknown;
|
||||
try {
|
||||
|
|
@ -100,9 +115,16 @@ export function parseSkillFrontmatter(yamlText: string): ParsedFrontmatter {
|
|||
} catch (err) {
|
||||
return { error: err instanceof Error ? err.message : String(err) };
|
||||
}
|
||||
// `null` is what an empty (or comment-only) block parses to — a real, if
|
||||
// degenerate, mapping of no fields rather than a malformed one.
|
||||
if (parsed === null || parsed === undefined) return { fields: {} };
|
||||
// An empty (or comment-only) block parses to `null`, and so does an explicit
|
||||
// `null` / `~` scalar — but only the first is a degenerate mapping of no
|
||||
// fields. The second is a non-mapping document, and returning `{ fields: {} }`
|
||||
// for it contradicts this function's own contract (Copilot). They are told
|
||||
// apart by the SOURCE, since the parsed value cannot distinguish them.
|
||||
if (parsed === null || parsed === undefined) {
|
||||
return hasContent(yamlText)
|
||||
? { error: "Frontmatter is not a YAML mapping of fields." }
|
||||
: { fields: {} };
|
||||
}
|
||||
if (typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||
return {
|
||||
error: "Frontmatter is not a YAML mapping of fields.",
|
||||
|
|
|
|||
Loading…
Reference in a new issue