Repository navigation
feat(catalog): not-a-skill-no-frontmatter, with a suggested frontmatter block #558
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
napetrov
wants to merge
58
commits into
intel:main
Choose a base branch
from
napetrov:feat/not-a-skill-no-frontmatter
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+564
−14
Open
Changes from all commits
Commits
Show all changes
58 commits
Select commit
Hold shift + click to select a range
6145871
feat(catalog): not-a-skill-no-frontmatter with a frontmatter hint
napetrov dfc3811
fix(catalog): frontmatter after a blank line is a broken skill, not p…
napetrov 337cc4e
fix(catalog): annotate the paragraph list in no_frontmatter for mypy
napetrov 14b3b3e
fix(catalog): a shorter fence run does not end a fence in the hint
napetrov 7a679d0
fix(catalog): quote the hint's name so YAML reads it as text
napetrov 6546d14
Merge branch main into feat/not-a-skill-no-frontmatter
napetrov a6d149f
Address review: take the hint paragraph after the first H1
napetrov d49c7d0
Address review: the hint names a skill below the root by its directory
napetrov bef9b4d
Address review: an indented frontmatter is a broken skill, not plain …
napetrov ef01bd7
Address review: the docstrings say a plain-Markdown SKILL.md gets a r…
napetrov 809a57d
Address review: drop the unreachable blockquote branch of the hint pa…
napetrov a390071
Address review: indent a multi-line message under its catalog row
napetrov b5deabe
Address review: the docs say which name the hint takes
napetrov 073e5be
Merge branch 'main' of https://github.com/intel/dokimi into feat/not-…
napetrov 07e9c5b
Self-review: keep the hint valid YAML; drop a duplicate blank-line test
napetrov 2774571
Address review: the docs say which name the hint takes
napetrov 1e62209
catalog check: the --ignore refusal for not-a-skill-no-frontmatter na…
napetrov 7944394
Address review: a tab after the # is an H1 too
napetrov 54bdede
Merge branch 'main' of https://github.com/intel/dokimi into feat/not-…
napetrov 2fc0bf6
Name not-a-skill-no-frontmatter among the refused --ignore codes
antonwolfy e8ee559
Skip multi-line HTML comments in the hint, and give an empty file no …
antonwolfy 9f02a72
Leave a frontmatter block after another line to the loader's error
antonwolfy 9539b14
Pin the root name, the plain-Markdown count, an empty file and a BOM
antonwolfy 56c6f9b
Pin the late-frontmatter window and a BOM-only file in tests
antonwolfy 05d06ec
Merge branch 'main' of https://github.com/intel/dokimi into feat/not-…
napetrov 40e0ca1
Address review: a fenced frontmatter example is not misplaced frontma…
napetrov eed07be
Thread: PRR_kwDOVBg7g88AAAABXXuAkA: already fixed in 56c6f9b
napetrov 29cb3e6
Thread: PRR_kwDOVBg7g88AAAABXYe8CA: already fixed in 9539b14
napetrov dec4522
Thread: PRR_kwDOVBg7g88AAAABXZISQA: already fixed in 40e0ca1fe
napetrov 3bd466f
fix(external): a quoted frontmatter example is not misplaced frontmatter
vlad-perevezentsev 15534b1
Merge branch 'main' of https://github.com/intel/dokimi into feat/not-…
napetrov 4052992
Address review: a SKILL.md that opens with a --- rule and prose gets …
napetrov 6b4c7a9
Address review: an H1 inside an HTML comment is not the title
napetrov e219942
Address review: a resumed finding with no code is not plain Markdown
napetrov 1be5501
Address review: a setext H1 is the title
napetrov 8ebd4a0
Address review: an indented code block after the H1 is not the descri…
napetrov c071ff5
Address review: pin the 64-character name limit for the H1 and the di…
napetrov 44386da
Address review: the root name falls back to the directory when the H1…
napetrov 3b1b878
Address review: a --- block with a YAML key is frontmatter, not a rule
napetrov 8e1f2d5
Merge branch 'main' of https://github.com/intel/dokimi into feat/not-…
napetrov e773c3c
fix(catalog): quoted, flow and list keys open frontmatter, not prose
xaleryb 16b5d9f
fix(catalog): a Word: prose line does not make an unclosed rule front…
xaleryb 2fed3f6
Merge branch 'feat/not-a-skill-no-frontmatter' of https://github.com/…
napetrov b7fa886
Thread: IC_kwDOVBg7g88AAAABmDJTQw: already fixed in 16b5d9f6a
napetrov 4c63e3e
Merge branch 'main' into feat/not-a-skill-no-frontmatter
xaleryb 58a6fdd
test(catalog): drop a note reference the repository does not define
xaleryb 364d8da
Merge branch 'main' into feat/not-a-skill-no-frontmatter
xaleryb da1f8fe
Merge branch 'main' of https://github.com/intel/dokimi into feat/not-…
napetrov 63ff037
Address review: a frontmatter block of blank lines is a broken skill
napetrov 3b25b70
Address review: HTML comments are taken out of the hint text
napetrov a8c2aa1
Address review: pin the classifier branches and the late-frontmatter …
napetrov 7c4a02f
Keep a comment marker that does not open a comment in the hint
napetrov 0077698
Keep a comment or code span inside its paragraph in the hint
napetrov fccb593
Bound the comment and code-span search in the hint
napetrov c4f27c6
Make the hint's code-span search linear in a backtick run
napetrov fcd55c7
Document the late-frontmatter window in external-content.md
antonwolfy d1a8ea0
Pin the comment and code-span branches of the hint in tests
antonwolfy c70579a
fix(catalog): keep the loader's error for odd frontmatter blocks
vchamarthi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
3 changes: 3 additions & 0 deletions
3
dokimi/changelog.d/20261109_170000_napetrov_no-frontmatter.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| ### Added | ||
|
|
||
| - **`catalog check` says how to turn a plain-Markdown `SKILL.md` into a skill.** A `SKILL.md` with no frontmatter still fails to load, and now has one finding, `not-a-skill-no-frontmatter`, whose message holds a `name`/`description` block made from the file's H1 and first paragraph. The summary line counts these apart from the other errors (`3 error, of them 2 plain Markdown with no frontmatter`), so a scan of a large corpus can tell prose files from broken skills. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| # Copyright (C) 2026 Intel Corporation | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| """A ``SKILL.md`` with no frontmatter: plain Markdown, not a broken skill. | ||
|
|
||
| Such a file does not load (the loader's error says "no frontmatter"). :mod:`dokimi.external.validate` | ||
| gives it one ``error`` finding, ``not-a-skill-no-frontmatter``, whose message holds a frontmatter block | ||
| made from the file's H1 and first paragraph, and ``catalog check`` counts these apart from the other | ||
| load errors in its summary. In the GitSkills corpus they are 165K of 1.88M distinct ``SKILL.md`` files, | ||
| 141K of them starting with a ``#`` heading. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import json | ||
| import re | ||
|
|
||
| from dokimi.external.skill_markdown import outside_fences | ||
|
|
||
| NO_FRONTMATTER = "not-a-skill-no-frontmatter" | ||
| NAME_MAX = 64 | ||
| SPEC_NAME = re.compile(r"[a-z0-9]+(?:-[a-z0-9]+)*") # the spec's name pattern (validate.NAME_PATTERN) | ||
| _NON_PRINTABLE = re.compile(r"[^\t\n\r\x20-\x7e\x85\xa0-\ud7ff\ue000-\ufffd\U00010000-\U0010ffff]") # PyYAML's reader | ||
| DESCRIPTION_HINT_MAX = 300 | ||
| SETEXT_UNDERLINE_RE = re.compile(r"=+") # the underline of a setext H1 (a --- one is a rule or an H2) | ||
| # Code spans and inline comments stay in their paragraph and within 1024 characters (bounded: linear time). | ||
| # A comment that opens a line may run over blank lines; unclosed, it runs to the end. | ||
| _COMMENT_RE = re.compile( | ||
| r"(?<!`)(`+)(?!`)(?:(?!\n[ \t]{0,1024}\n).){1,1024}?(?<!`)\1(?!`)|^[ \t]*<!--(?!.*?-->).*\Z|^[ \t]*<!--.*?-->|<!--(?:(?!\n[ \t]{0,1024}\n).){0,1024}?-->", | ||
| re.S | re.M, | ||
| ) | ||
|
|
||
|
|
||
| def no_frontmatter_message(lines: list[str], dir_name: str, at_root: bool = False) -> str: | ||
| """The ``not-a-skill-no-frontmatter`` message, with a frontmatter block made from the H1 and the first paragraph. | ||
|
|
||
| Below the scanned root the name is the directory's when that is a valid spec name, since the | ||
| loader's ``name-mismatch`` check compares the name with it; otherwise (and at the root) it is the | ||
| H1 in kebab-case (ASCII letters and digits; the directory name when the H1 has none, or there is | ||
| no H1). The description is the first paragraph after the H1, cut at a word near 300 characters. | ||
| The block is a start: the description still needs a "when" clause. | ||
| """ | ||
| # CommonMark fences: a shorter run or another marker does not close one | ||
| raw = _without_comments("\n".join(line.lstrip("\ufeff") for line in outside_fences("\n".join(lines)))) | ||
| body = [line.strip() for line in raw] | ||
| title = "" | ||
| start = 0 # the paragraph is the one after the first H1: an intro or a badge above it is not taken | ||
| for i, stripped in enumerate(body): | ||
|
napetrov marked this conversation as resolved.
Resolved
|
||
| if stripped[:2] in ("# ", "#\t"): # CommonMark: a space or a tab after the # | ||
|
napetrov marked this conversation as resolved.
Resolved
|
||
| title = stripped[2:].strip() | ||
| start = i + 1 | ||
| break | ||
| if stripped and i + 1 < len(body) and SETEXT_UNDERLINE_RE.fullmatch(body[i + 1]): # ``Title`` over ``===`` | ||
| title = stripped | ||
| start = i + 2 | ||
| break | ||
| paragraph = _first_paragraph(raw[start:]) | ||
| if not at_root and SPEC_NAME.fullmatch(dir_name) and len(dir_name) <= NAME_MAX: | ||
| name = dir_name # validate.NAME_PATTERN, as the loader's name-mismatch check reads it | ||
| else: | ||
| name = _kebab(title) or _kebab(dir_name) or "my-skill" | ||
| description = _NON_PRINTABLE.sub("", " ".join(paragraph)) # what PyYAML refuses in a plain or quoted scalar | ||
| if len(description) > DESCRIPTION_HINT_MAX: | ||
| description = description[:DESCRIPTION_HINT_MAX].rsplit(" ", 1)[0] + " ..." | ||
| description = description or f"What {name} does. Use when ..." | ||
| # Always quoted: YAML reads a name such as "2048", "yes" or "2026-02-30" as a number, a bool or a date. | ||
| name_text = json.dumps(name) | ||
| block = f"---\nname: {name_text}\ndescription: {json.dumps(description, ensure_ascii=False)}\n---" | ||
| return ( | ||
| "plain Markdown with no frontmatter: no harness offers it as a skill. To make it one, start " | ||
| f"the file with (then add when to use it to the description):\n{block}" | ||
| ) | ||
|
|
||
|
|
||
| def _without_comments(text: str) -> list[str]: | ||
| """The lines of ``text`` with its HTML comments taken out; text around a comment on its line is kept.""" | ||
| return _COMMENT_RE.sub(lambda m: m.group(0) if m.group(0).startswith("`") else "", text).split("\n") | ||
|
|
||
|
|
||
| def _first_paragraph(raw: list[str]) -> list[str]: | ||
| """The lines of the first paragraph of ``raw`` (unstripped), skipping blank, heading, table and HTML lines, | ||
| and an indented code block before it (four spaces or a tab).""" | ||
| paragraph: list[str] = [] | ||
| for line in raw: | ||
| stripped = line.strip() | ||
| if not paragraph and line.startswith(("\t", " ")) and stripped: | ||
| continue # an indented code block is not prose | ||
| if not stripped or stripped.startswith(("#", "|", "<", "!", "---")): | ||
| if paragraph: | ||
| break | ||
| continue | ||
| paragraph.append(stripped) # outside_fences has taken the quote markers off already | ||
| return paragraph | ||
|
|
||
|
|
||
| def _kebab(text: str) -> str: | ||
| """``text`` as a spec name: ASCII lowercase letters and digits joined by single hyphens, at most 64 characters.""" | ||
| return re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-")[:NAME_MAX].strip("-") | ||
|
napetrov marked this conversation as resolved.
Resolved
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.