This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow-steward.git


The following commit(s) were added to refs/heads/main by this push:
     new 6e560f1  feat(skill-validator): enforce Claude Code's 1,536-char 
metadata budget (#104)
6e560f1 is described below

commit 6e560f100d961bfd379121fd2b68b43ee4ac2841
Author: Yeonguk Choo <[email protected]>
AuthorDate: Mon May 11 02:09:00 2026 +0900

    feat(skill-validator): enforce Claude Code's 1,536-char metadata budget 
(#104)
    
    * skills/pr-management-code-review: trim frontmatter to fit metadata budget
    
    * feat(skill-validator): enforce Claude Code's 1,536-char metadata budget
---
 .claude/skills/pr-management-code-review/SKILL.md  | 27 ++++++++--------------
 .../src/skill_validator/__init__.py                | 23 +++++++++++++++++-
 tools/skill-validator/tests/test_validator.py      | 24 +++++++++++++++++++
 3 files changed, 55 insertions(+), 19 deletions(-)

diff --git a/.claude/skills/pr-management-code-review/SKILL.md 
b/.claude/skills/pr-management-code-review/SKILL.md
index 0fdc490..79fc7f4 100644
--- a/.claude/skills/pr-management-code-review/SKILL.md
+++ b/.claude/skills/pr-management-code-review/SKILL.md
@@ -2,25 +2,16 @@
 name: maintainer-review
 mode: Triage
 description: |
-  Walk a maintainer through deep code review of open pull requests on the 
configured `<upstream>` repo (default: read from `<project-config>/project.md → 
upstream_repo`). The
-  default working list — referred to throughout the docs as **"my reviews"** — 
is the union of five signals on the
-  authenticated maintainer: PRs where review is requested from them, PRs that 
touch files they recently modified, PRs
-  whose changed files they own per `CODEOWNERS`, PRs that `@`-mention them, 
and PRs they already submitted a real
-  review on (triage comments do not count). Filters can narrow by area label, 
collaborator status, or to a single PR.
-  For each PR the skill reads the diff, applies the project's
-  review criteria (the source files declared in
-  `<project-config>/pr-management-code-review-criteria.md`, plus
-  the project's repo-wide `AGENTS.md`), runs any
-  locally-configured adversarial reviewer (e.g. the OpenAI
-  Codex plugin), surfaces findings, drafts an `approve` / `request-changes` / 
`comment` review with
-  inline comments proposed by default, and — on the maintainer's confirmation 
— posts it via the
-  `addPullRequestReview` mutation. This is the deep-review counterpart to the 
triage skill.
+  Walk a maintainer through deep, sequential code review of open pull requests 
on the configured `<upstream>` repo.
+  Defaults to the **"my reviews"** queue (the union of five maintainer signals 
— see the Inputs table); selectors can
+  narrow to a single PR, an area label, or a collaborator subset. Drafts an 
`approve` / `request-changes` / `comment`
+  review per PR and posts on the maintainer's confirmation. Deep-review 
counterpart to the triage skill.
 when_to_use: |
-  Invoke when a maintainer says "review my PRs", "go through the PRs assigned 
to me", "review my queue", "review the
-  area:scheduler PRs", "review PR NNN", "do my review pass", or any variation 
on "look over the code on PRs I'm
-  responsible for, one at a time." Distinct from `pr-management-triage`, which 
decides *whether* to engage with a PR. This skill is
-  invoked **after** triage has produced PRs marked `ready for maintainer 
review` (or any other curated selector) and a
-  human reviewer is doing the actual code review.
+  Invoke when a maintainer says "review my PRs", "go through my review queue", 
"review PR NNN", "review the
+  area:scheduler PRs", "do my review pass", or any variation on "look over PRs 
I'm responsible for, one at a time."
+  Also fires on "review my CODEOWNER PRs", "pair this PR with Codex / 
adversarial review", and "review the
+  ready-for-maintainer-review queue". Distinct from `pr-management-triage` 
(which decides *whether* to engage);
+  this skill runs **after** triage has produced reviewable PRs.
 license: Apache-2.0
 ---
 <!-- SPDX-License-Identifier: Apache-2.0
diff --git a/tools/skill-validator/src/skill_validator/__init__.py 
b/tools/skill-validator/src/skill_validator/__init__.py
index 3f4cb57..40b50ba 100644
--- a/tools/skill-validator/src/skill_validator/__init__.py
+++ b/tools/skill-validator/src/skill_validator/__init__.py
@@ -106,6 +106,14 @@ FRAMEWORK_PLACEHOLDERS: tuple[str, ...] = (
     "<repo>",
 )
 
+# YAML block-scalar headers — must not be stored as scalar content,
+# else MAX_METADATA_CHARS measurements get inflated.
+YAML_BLOCK_SCALAR_HEADERS = {"|", ">", "|-", "|+", ">-", ">+"}
+
+# Per-skill description + when_to_use budget; Claude Code truncates past this.
+# https://code.claude.com/docs/en/skills#frontmatter-reference
+MAX_METADATA_CHARS = 1536
+
 # Markdown link pattern: [text](url)
 LINK_PATTERN = re.compile(r"\[([^\]]+)\]\(([^)]+)\)")
 
@@ -184,7 +192,8 @@ def parse_frontmatter(text: str) -> dict[str, str] | None:
                     result[current_key] = 
"\n".join(current_value_lines).strip()
                 key, _, value = line.partition(":")
                 current_key = key.strip()
-                current_value_lines = [value.strip()] if value.strip() else []
+                inline = value.strip()
+                current_value_lines = [inline] if inline and inline not in 
YAML_BLOCK_SCALAR_HEADERS else []
                 continue
             # Line without colon that is not indented — treat as folded scalar
             if current_key is not None:
@@ -229,6 +238,18 @@ def validate_frontmatter(path: Path, text: str) -> 
Iterable[Violation]:
             f"frontmatter mode '{fm['mode']}' not in {sorted(ALLOWED_MODES)} 
(see docs/modes.md)",
         )
 
+    desc_len = len(fm.get("description", ""))
+    wtu_len = len(fm.get("when_to_use", ""))
+    total = desc_len + wtu_len
+    if total > MAX_METADATA_CHARS:
+        yield Violation(
+            path,
+            1,
+            f"description + when_to_use is {total} chars; "
+            f"Claude Code truncates past {MAX_METADATA_CHARS} "
+            f"(description={desc_len}, when_to_use={wtu_len})",
+        )
+
 
 # ---------------------------------------------------------------------------
 # Link validation
diff --git a/tools/skill-validator/tests/test_validator.py 
b/tools/skill-validator/tests/test_validator.py
index d231729..bbc157f 100644
--- a/tools/skill-validator/tests/test_validator.py
+++ b/tools/skill-validator/tests/test_validator.py
@@ -25,6 +25,7 @@ import pytest
 
 from skill_validator import (
     FORBIDDEN_PATTERNS,
+    MAX_METADATA_CHARS,
     extract_headings,
     find_repo_root,
     parse_frontmatter,
@@ -132,6 +133,29 @@ class TestValidateFrontmatter:
         violations = list(validate_frontmatter(path, text))
         assert violations == []
 
+    def test_metadata_under_limit(self, tmp_path: Path) -> None:
+        path = tmp_path / "SKILL.md"
+        desc = "a" * 800
+        wtu = "b" * 700
+        text = f"---\nname: foo\ndescription: {desc}\nwhen_to_use: 
{wtu}\nlicense: Apache-2.0\n---\n"
+        violations = list(validate_frontmatter(path, text))
+        assert violations == []
+
+    def test_metadata_over_limit(self, tmp_path: Path) -> None:
+        path = tmp_path / "SKILL.md"
+        desc = "a" * 1000
+        wtu = "b" * (MAX_METADATA_CHARS - 1000 + 1)
+        text = f"---\nname: foo\ndescription: {desc}\nwhen_to_use: 
{wtu}\nlicense: Apache-2.0\n---\n"
+        violations = list(validate_frontmatter(path, text))
+        assert any("truncates" in v.message and str(MAX_METADATA_CHARS) in 
v.message for v in violations)
+
+    def test_metadata_block_scalar_indicator_not_counted(self) -> None:
+        text = f"---\nname: foo\ndescription: |\n  {'a' * 100}\nlicense: 
Apache-2.0\n---\n"
+        fm = parse_frontmatter(text)
+        assert fm is not None
+        assert not fm["description"].startswith("|")
+        assert len(fm["description"]) == 100
+
 
 # ---------------------------------------------------------------------------
 # Heading / anchor helpers

Reply via email to