Skip to content

fix(skills): parse YAML block scalars in SKILL.md frontmatter#1908

Merged
Hmbown merged 3 commits into
Hmbown:mainfrom
zlh124:feat/skill-yaml-block-scalar
May 25, 2026
Merged

fix(skills): parse YAML block scalars in SKILL.md frontmatter#1908
Hmbown merged 3 commits into
Hmbown:mainfrom
zlh124:feat/skill-yaml-block-scalar

Conversation

@zlh124
Copy link
Copy Markdown
Contributor

@zlh124 zlh124 commented May 21, 2026

Summary

Fixes YAML block scalar parsing in SKILL.md frontmatter descriptions. The parser previously only handled single-line quoted/unquoted values — multi-line descriptions using > or | were silently parsed as the literal indicator string.

Changes

crates/tui/src/skills/mod.rs — extended SkillRegistry::parse_skill:

  • Block scalar detection: recognizes >, >-, >+, |, |-, |+ indicators
  • Indentation-based content collection: continuation lines are gathered by comparing each line's indent against the base indent of the key line (per YAML spec)
  • Three chomping modes:
    • strip (-): removes all trailing blank lines
    • clip (default): keeps at most one trailing newline
    • keep (+): preserves all trailing blank lines
  • Folded (>): non-empty lines joined with spaces; empty lines become paragraph breaks (\n)
  • Literal (|): lines joined with newlines
  • Single-line descriptions remain fully backward compatible

Tests

13 unit tests added covering all six block-scalar variants plus chomping edge cases:

Test Scenario
parse_skill_folded_block_scalar > folded block scalar
parse_skill_folded_strip_block_scalar >- folded + strip via render pipeline
parse_skill_literal_block_scalar | literal block scalar
parse_skill_direct_folded_result Direct > assertion on Skill struct
parse_skill_strip_chomp_strips_trailing_empties >- strips trailing empties
parse_skill_keep_chomp_preserves_trailing_empties >+ preserves trailing empties
parse_skill_clip_chomp_clips_excess_trailing_empties > clip with 3 trailing empties
parse_skill_clip_chomp_no_trailing_empties > clip with 0 trailing empties
parse_skill_clip_chomp_one_trailing_empty > clip with 1 trailing empty
parse_skill_strip_vs_keep_trailing >- vs >+ comparison
parse_skill_literal_strip_strips_trailing_newlines |- literal strip
parse_skill_literal_keep_preserves_trailing_newlines |+ literal keep
parse_skill_single_line_description_still_works Single-line backward compat

All pass:

test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured

Related issue

Fixes: #1907

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the skill frontmatter parser to support YAML block scalars, including folded ('>') and literal ('|') styles along with chomping indicators. It also adds several unit tests to validate these features. Review feedback highlights two issues regarding YAML specification compliance: the loss of relative indentation due to excessive trimming and the incorrect implementation of the 'clip' chomping mode, which should ensure a single trailing newline for non-empty blocks.

Comment on lines +277 to +294
let mut block_lines: Vec<&str> = Vec::new();
i += 1;
while i < lines.len() {
let raw_line = lines[i];
if raw_line.trim().is_empty() {
// Empty lines are part of the block
block_lines.push("");
i += 1;
continue;
}
let line_indent = raw_line.len() - raw_line.trim_start().len();
if line_indent > base_indent {
block_lines.push(raw_line.trim());
i += 1;
} else {
break;
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation of block scalar parsing loses relative indentation within the block because it calls raw_line.trim() on every line. According to the YAML specification, a block scalar has a determined indentation level (usually from the first non-empty line), and only that amount of leading whitespace should be stripped from each line. This ensures that nested structures like lists or indented code blocks within the description are preserved.

Suggested change
let mut block_lines: Vec<&str> = Vec::new();
i += 1;
while i < lines.len() {
let raw_line = lines[i];
if raw_line.trim().is_empty() {
// Empty lines are part of the block
block_lines.push("");
i += 1;
continue;
}
let line_indent = raw_line.len() - raw_line.trim_start().len();
if line_indent > base_indent {
block_lines.push(raw_line.trim());
i += 1;
} else {
break;
}
}
let mut block_lines: Vec<&str> = Vec::new();
let mut block_indent = None;
i += 1;
while i < lines.len() {
let raw_line = lines[i];
let trimmed = raw_line.trim_start();
if trimmed.is_empty() {
block_lines.push("");
i += 1;
continue;
}
let line_indent = raw_line.len() - trimmed.len();
if line_indent > base_indent {
let b_indent = *block_indent.get_or_insert(line_indent);
block_lines.push(raw_line.get(b_indent..).unwrap_or(trimmed).trim_end());
i += 1;
} else {
break;
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix already

Comment on lines +309 to +318
// clip: keep at most one trailing empty line
let mut lines = block_lines;
while lines.len() >= 2
&& lines[lines.len() - 1].is_empty()
&& lines[lines.len() - 2].is_empty()
{
lines.pop();
}
lines
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The clip chomping mode (the default for > and |) should ensure the resulting string ends with exactly one newline character if the block is not empty. The current implementation only preserves a trailing newline if the input block already ended with one or more empty lines. If the block ends with a non-empty line, the current code produces a string without a trailing newline, which deviates from the YAML specification.

@zlh124 zlh124 force-pushed the feat/skill-yaml-block-scalar branch from 1fd6f32 to 32fcfc4 Compare May 21, 2026 15:06
@zlh124 zlh124 force-pushed the feat/skill-yaml-block-scalar branch from 32fcfc4 to 6041a60 Compare May 21, 2026 15:24
@zlh124
Copy link
Copy Markdown
Contributor Author

zlh124 commented May 21, 2026

Maybe use a yaml parser is better. but for now, is ok.

@Hmbown
Copy link
Copy Markdown
Owner

Hmbown commented May 24, 2026

Stewardship note: linked #1907 into v0.8.44 and left it open until this lands. Checks are green right now, and the review focus should be parser scope/backward compatibility rather than UI text only, since the parsed description feeds /skills, load_skill, and model prompt context.

Thanks @zlh124 for the clear repro and tests.

@zlh124
Copy link
Copy Markdown
Contributor Author

zlh124 commented May 25, 2026

Stewardship note: linked #1907 into v0.8.44 and left it open until this lands. Checks are green right now, and the review focus should be parser scope/backward compatibility rather than UI text only, since the parsed description feeds /skills, load_skill, and model prompt context.

Thanks @zlh124 for the clear repro and tests.

谢谢你,鲸鱼兄弟!

@Hmbown Hmbown merged commit de9937c into Hmbown:main May 25, 2026
8 checks passed
@Hmbown Hmbown added this to the v0.8.45 milestone May 25, 2026
Hmbown added a commit that referenced this pull request May 25, 2026
- Update @reidliu41 entry to add user-message transcript highlight (#1995)
- Update @zlh124 entry to add SKILL.md YAML block-scalar parser (#1908)
- Update @h3c-hexin entry to add file_search cancellation report (#2044)
- Add @lpeng1711694086-lang for user-message TUI contrast feature request (#1672)
Hmbown added a commit that referenced this pull request May 25, 2026
- Update @reidliu41 entry to add user-message transcript highlight (#1995)
- Update @zlh124 entry to add SKILL.md YAML block-scalar parser (#1908)
- Update @h3c-hexin entry to add file_search cancellation report (#2044)
- Add @lpeng1711694086-lang for user-message TUI contrast feature request (#1672)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SKILL.md YAML block scalar description not parsed correctly

2 participants