Add repository-local tutorial agent skills (#218)

* Add repository-local tutorial agent skills

* Enforce tutorial writer safety boundaries

* Reduce trace verifier complexity

* Add cross-model tutorial writing review

* Refresh apt metadata in libbpf CI

* Split tutorial workflow and writing style skills

* Check complete tutorial source blocks

* Harden the tutorial writing workflow

* Refactor source block validation

* Close tutorial workflow isolation gaps

* Test the isolated writer trace verifier

* Keep complete-source synchronization byte exact

* Separate tutorial and Skill review gates

* Simplify the tutorial writing workflow

* Keep tutorial source and explanations together

* Keep public tutorial references self-contained

* Restore tutorial checker executable bits

* Refine tutorial writing workflow

* Prefer positive tutorial prose

* Add diagram guidance to tutorial skills

* Standardize tutorial source links

---------

Co-authored-by: LinuxDev9002 <i@littlefisher.me>
This commit is contained in:
云微
2026-07-19 14:56:27 -07:00
committed by GitHub
parent c054586a39
commit d055a0833f
16 changed files with 1344 additions and 1 deletions

View File

@@ -0,0 +1,127 @@
---
name: write-bpf-production-tutorial
description: Create or revise a tested bilingual bpf-developer-tutorial lesson from the implementation and real runtime evidence. Use for tutorial scope, pinned Claude Opus 4.5 authorship, README drafting, technical flow diagrams, exact source inclusion, host builds, KVM tests, supervised completion, and PR preparation. Pair with bpf-tutorial-writing-style for prose decisions.
---
# Write a BPF Tutorial
Keep the workflow small. Claude Opus 4.5 writes all tutorial prose. Codex supervises source fidelity, actual completion, bilingual consistency, validation, and publication. Do not add other writers, reviewer panels, task ledgers, reviewer databases, or extra process files.
Invoke `$bpf-tutorial-writing-style` before writing or reviewing README prose.
## Pinned writer
Use the exact model ID `claude-opus-4-5-20251101` through Claude Code. Never use `opus`, `latest`, a default model, a fallback model, or another provider for tutorial prose. If this exact model is unavailable, stop and report the block instead of substituting another writer.
Use exactly one non-interactive Claude invocation for one lesson. Require Opus to edit the existing README files in place, one specific sentence or paragraph at a time and in document order. Each Edit operation must have a narrow target. It must not generate a replacement article and overwrite the file as a whole. It must preserve the existing code-block positions while revising the prose around each block, first completing natural Chinese and then matching English from the same facts.
The initial prompt must contain the complete task, source-fidelity rules, style rules, and final audit checklist. Opus must not ask Codex or the user a question, pause for confirmation, return a partial progress report, or stop after one language or one section. It may return only after both README files have been rewritten from beginning to end and it has run its own baseline comparison and formatting checks. Do not split the main rewrite into planned paragraph batches, switch models, or let Codex fill in prose afterward.
A typical non-interactive invocation is:
```bash
claude -p \
--model claude-opus-4-5-20251101 \
--effort high \
--permission-mode acceptEdits \
--allowedTools 'Read,Edit,Grep,Glob,Bash(git show *),Bash(git diff *),Bash(git status *)' \
--output-format json
```
Give Opus access only to the lesson, its implementation and tests, the selected precedents, these repository-local Skills, and read-only baseline inspection through `git show` and `git diff`. Tell it to edit only the English and Chinese README pair and never commit, push, or publish.
## 1. Prepare the fact pack and technical draft
Codex prepares the complete input before invoking Opus. Read the entry README pair, implementation, tests, Makefile, captured output, and primary upstream references. Read these bilingual precedents in full:
- <https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/47-cuda-events> for end-to-end depth;
- <https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/49-hid> for an approachable teaching voice;
- at most one closer completed lesson when it contributes a subsystem-specific pattern.
Build a private fact pack that accounts for every mechanism, number, version, requirement, failure path, cleanup behavior, limitation, output field, and reference that the final lesson must retain. Include useful verified background from source code and primary upstream material. More context is helpful only when it is source-grounded; do not pad the pack with plausible but unverified history, adoption, performance, or production claims.
Prepare a complete technical draft before Opus writes. For an existing lesson, the entry README pair is the initial draft: repair factual gaps and add verified missing context without polishing the voice. For a new lesson, write a direct, comprehensive bilingual technical draft from the fact pack. Completeness and traceability matter more than style at this stage. Keep the fact pack private and do not add planning files to the repository.
Write down two private working sentences as part of the fact pack: who the reader is, and what they should be able to explain or run at the end.
Choose an honest scope. A lesson may teach a small operational tool or a focused kernel feature. Do not call a lab demo production-ready, and do not invent a production story that the CLI, output, and tests cannot support.
Decide once whether a technical diagram materially improves the lesson. A diagram earns its place when the reader must track at least three dependent state changes, a branch or retry loop, ownership across kernel and user space, or one source that affects several downstream components. A short linear filter or one-step attachment usually reads better as prose.
Codex prepares technical diagrams from the verified fact pack before the Opus prose pass. Prefer a reviewable text source such as Graphviz DOT or Mermaid, or a directly maintained SVG, and commit that source beside the rendered PNG. Keep one language-neutral diagram when code identifiers carry the meaning; use paired Chinese and English assets only when prose labels are essential. Technical flowcharts come from code and tests rather than generative illustration.
Render the asset deterministically and record a reproducible command in the fact pack. Use a descriptive image name, readable text at normal README width, restrained color, and a clear branch or ownership direction. Published Markdown uses descriptive alt text and a canonical absolute GitHub image target such as `https://github.com/eunomia-bpf/bpf-developer-tutorial/raw/main/src/<lesson>/<diagram>.png`. Give Opus the finished asset and source so it can introduce the diagram, connect it to the running example, and explain the important branch immediately afterward.
## 2. Verify the implementation
Build on the host. Use `$test-bpf-tutorial-kvm` for load, attach, and runtime behavior when the host kernel cannot run the feature safely. Exercise the normal path, one meaningful failure or boundary path, and cleanup. Record the commands, guest kernel version and commit, and real output needed by the README.
Do not turn the README into a validation transcript. Runtime provenance supports the tutorial, but the reader-facing mechanism remains the story.
## 3. Give the complete pack to Opus
Give pinned Opus the entry pair, the complete technical draft, the fact pack, implementation and evidence, and the project-provided advanced tutorial guidelines through `$bpf-tutorial-writing-style`. The initial prompt must explicitly separate authoritative lesson facts from voice-only examples. Opus may reorganize and rewrite the supplied facts, but it must not introduce technical content from a style sample or precedent unless that content already appears in the fact pack.
The lesson must start from one short, truthful scene that the implementation can reproduce, then follow one packet, event, task, device interaction, or failure through the whole kernel/user-space path. Use the scene to raise the technical question before naming every mechanism, requirement, counter, and limitation. Never invent a customer, outage, production deployment, performance result, or tool capability to make the opening sound important.
Include the complete core source exactly as implemented, then walk through the important mechanisms. Tell Opus explicitly to use repeated local edits in document order and to continue without returning until the last paragraph of the second language is complete. Replacing the complete README in one write is a workflow failure even when the final prose looks acceptable.
Follow the selected precedent's component rhythm. Introduce one component, present its complete source inline, and explain its important logic before moving to the next component. Do not hide source in `<details>` or collect every file into one uninterrupted source dump. The source must remain searchable, copyable, and byte-exact.
Explain advanced eBPF behavior, not ordinary C syntax. Keep build, run, expected output, requirements, summary, and primary references. Explain cleanup as part of the normal mechanism instead of turning it into a warning section. Compress failure conditions, limitations, and safety boundaries into one short paragraph of at most two sentences near the end.
Use one opening complete-source callout whose only target is the lesson directory at `https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/<lesson>`. The directory is the single entry point for implementation files, the `Makefile`, fixtures, and tests; introduce individual filenames as code labels beside their complete blocks instead of turning the opening into a file-link catalog.
Use canonical GitHub absolute URLs for every other Markdown link. Never publish a `./` or `../` target or a link to another host. If no stable GitHub URL exists, omit the link. This repository rule overrides link examples in older lessons and the advanced guideline's website call to action.
Do not publish local infrastructure details. Keep workspace paths, shared lab repository names, VM names, host-to-guest copy steps, cache locations, and agent trace paths out of the README and PR description. Public reproducibility text may state the architecture, kernel version and commit, required configuration, commands, and captured output.
Opus must write Chinese as Chinese, using the voice sample and rules in `$bpf-tutorial-writing-style`, then write natural English with the same promise, structure, source, commands, evidence, and limits. It must not translate sentence by sentence. It must finish both files in the same lesson session.
## 4. Supervise actual completion
Do not accept Opus's final message as evidence that the work is complete. Inspect the files and diff. Confirm that both README files changed, every requested prose paragraph was actually rewritten, every source file still appears exactly once, and code, commands, captured output, versions, requirements, failure behavior, cleanup, limitations, and references remain intact.
For a full rewrite, compare prose outside fenced blocks with the entry version. Unchanged titles, tables, commands, output, and reference entries may be intentional. Unchanged explanatory paragraphs are evidence that the rewrite is incomplete unless the user explicitly exempted them. Inspect diff hunk distribution as well: changes concentrated only at the opening or ending do not satisfy a paragraph-by-paragraph rewrite.
When the lesson contains a diagram, inspect the rendered image as well as its source. Confirm that every node and edge follows the implementation, both languages introduce the same figure and draw the same conclusion, the PNG remains legible at README width, and the absolute GitHub image target points to the committed asset.
Codex performs this postcondition check after the one complete rewrite. If it finds a concrete defect, reject Opus's self-report and send one exact defect list back to the same pinned-model session. Opus must fix every listed issue without asking questions and rerun the final checks. This supervised correction is not permission to draft the lesson in batches: the first invocation must still attempt the entire Chinese and English pair before returning.
Codex may point out omissions, contradictions, awkward passages, broken links, or failed checks, but Opus must make every reader-facing prose revision. After the supervised Opus correction, Codex may apply a deterministic non-prose correction such as canonicalizing a verified link target, normalizing line endings, or restoring a source block byte-for-byte from its real file. It must not use this exception to rewrite wording, repair missing explanation, or introduce facts.
Preserve the model response, failed run, partial diff, prompt, and session history. Never delete, truncate, overwrite, or clean real conversation history, agent traces, prompts, partial runs, or failed runs.
## 5. Run one reader review
Read both files from top to bottom as an intermediate eBPF developer. Fix the text when:
- the opening reads like an abstract, specification, PR description, or test report;
- a large code wall arrives before the reader has a useful mental model;
- setup, conflicts, signals, KVM provenance, or limitations interrupt the main mechanism;
- a section catalogs facts without explaining cause and effect;
- the scenario promises a tool more capable than the implementation;
- Chinese follows English word order or switches languages unnecessarily;
- the reader reaches the end remembering only a feature name or validation transcript.
For a rewrite, compare the finished pair with the entry version once. Confirm that code, commands, captured output, versions, requirements, cleanup, the compact final boundary, and primary references remain accurate. Failure and limitation details may be condensed into the required two-sentence ending instead of surviving as a catalog. Every other removed passage should be repetition, stale framing, or material that moved to a better place. Word counts, heading counts, and repeated-term counts can expose bloat, but they are diagnostics rather than targets.
This is the normal review gate. Keep the pinned Opus session as the only prose editor. Codex reports concrete defects and reruns deterministic checks; Opus revises the affected paragraphs. The exact model identity is required by the workflow, but it is not evidence of quality. Only the finished artifact and passing checks are evidence.
## 6. Validate and publish
Check every complete-source block in the English and Chinese pair:
```bash
python3 .agents/skills/write-bpf-production-tutorial/scripts/sync-source-blocks.py \
--repo "$(git rev-parse --show-toplevel)" \
--readme src/<lesson>/README.md \
--readme src/<lesson>/README.zh.md \
--expected-source src/<lesson>/<tool>.bpf.c \
--expected-source src/<lesson>/<tool>.c \
--check
```
Repeat `--expected-source` for each complete shared header. Use ordinary Markdown fenced code blocks without HTML markers. Copy complete source from the real file, then run the checker; it is deliberately read-only.
Run `git diff --check`, the lesson build, the relevant host tests, the KVM runtime test, and repository documentation checks. Re-render every diagram from its committed text or vector source and confirm that the generated PNG matches the committed asset. Inspect the final diff for unintended files. Commit and push the repository-local Skills and tutorial changes only after these checks pass. Do not merge unless the user asks.

View File

@@ -0,0 +1,4 @@
interface:
display_name: "BPF Tutorial Writer"
short_description: "Write and test clear bilingual eBPF lessons"
default_prompt: "Use $write-bpf-production-tutorial to turn this eBPF example into a clear, tested bilingual tutorial."

View File

@@ -0,0 +1,174 @@
#!/usr/bin/env python3
"""Check exact source files embedded in standard Markdown code fences."""
from __future__ import annotations
import argparse
import collections
import pathlib
import re
import sys
FENCE_RE = re.compile(r"^(`{3,})([A-Za-z0-9_+.-]*)[ \t]*$")
LEGACY_MARKERS = ("<!-- BEGIN FULL SOURCE:", "<!-- END FULL SOURCE -->")
LANGUAGES = {
".c": "c",
".cc": "cpp",
".cpp": "cpp",
".go": "go",
".h": "c",
".py": "python",
".rs": "rust",
".sh": "bash",
}
def contained_path(root: pathlib.Path, value: str, label: str) -> pathlib.Path:
path = (root / value).resolve()
try:
path.relative_to(root)
except ValueError as error:
raise ValueError(f"{label} escapes repository root: {value}") from error
return path
def parse_fenced_blocks(readme: pathlib.Path) -> list[tuple[str, str]]:
text = readme.read_text(encoding="utf-8")
for marker in LEGACY_MARKERS:
if marker in text:
raise ValueError(
f"legacy complete-source HTML marker is not allowed in {readme.name}"
)
lines = text.splitlines(keepends=True)
blocks: list[tuple[str, str]] = []
index = 0
while index < len(lines):
opening = FENCE_RE.match(lines[index].rstrip("\r\n"))
if not opening:
index += 1
continue
fence, language = opening.groups()
end = index + 1
while end < len(lines) and lines[end].rstrip("\r\n") != fence:
end += 1
if end == len(lines):
raise ValueError(f"unclosed Markdown code fence in {readme.name}")
blocks.append((language, "".join(lines[index + 1 : end])))
index = end + 1
return blocks
def parse_arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("--repo", required=True, help="absolute repository root")
parser.add_argument(
"--readme", action="append", required=True, help="repository-relative README"
)
parser.add_argument(
"--expected-source",
action="append",
required=True,
help="repository-relative complete core source; repeat for the full inventory",
)
parser.add_argument(
"--check", action="store_true", required=True, help="check exact fenced source"
)
return parser.parse_args()
def resolve_readmes(
root: pathlib.Path, values: list[str]
) -> list[tuple[str, pathlib.Path]]:
readmes: list[tuple[str, pathlib.Path]] = []
seen: set[pathlib.Path] = set()
for value in values:
readme = contained_path(root, value, "README path")
if not readme.is_file():
raise ValueError(f"README does not exist: {value}")
if readme in seen:
raise ValueError(f"duplicate README path: {value}")
readmes.append((value, readme))
seen.add(readme)
if len(readmes) != 2:
raise ValueError("pass exactly the English and Chinese README pair")
names = {path.name for _, path in readmes}
parents = {path.parent for _, path in readmes}
if names != {"README.md", "README.zh.md"} or len(parents) != 1:
raise ValueError("README paths must be README.md and README.zh.md from one lesson")
return readmes
def source_payload(
root: pathlib.Path, value: str, readme_paths: set[pathlib.Path]
) -> tuple[str, str]:
source = contained_path(root, value, "expected source path")
if not source.is_file():
raise ValueError(f"expected source file does not exist: {value}")
if source in readme_paths:
raise ValueError(f"expected source cannot be a tutorial README: {value}")
if source.suffix not in LANGUAGES:
raise ValueError(f"unsupported expected-source file type: {value}")
source_bytes = source.read_bytes()
if not source_bytes:
raise ValueError(f"complete source cannot be empty: {value}")
if not source_bytes.endswith(b"\n"):
raise ValueError(f"complete source must end with an LF byte: {value}")
source_text = source_bytes.decode("utf-8")
if "\r" in source_text:
raise ValueError(f"complete source must use LF line endings: {value}")
return LANGUAGES[source.suffix], source_text
def check_readme(
value: str,
readme: pathlib.Path,
expected: collections.Counter[tuple[str, str]],
labels: dict[tuple[str, str], list[str]],
) -> None:
actual = collections.Counter(parse_fenced_blocks(readme))
for payload, count in expected.items():
found = actual[payload]
names = ", ".join(labels[payload])
if found < count:
raise ValueError(f"missing exact fenced source in {value}: {names}")
if found > count:
raise ValueError(f"duplicate exact fenced source in {value}: {names}")
def main() -> int:
args = parse_arguments()
root = pathlib.Path(args.repo).resolve()
if not (root / ".git").exists():
print(f"not a Git worktree: {root}", file=sys.stderr)
return 2
try:
readmes = resolve_readmes(root, args.readme)
readme_paths = {path for _, path in readmes}
labels: dict[tuple[str, str], list[str]] = collections.defaultdict(list)
expected: collections.Counter[tuple[str, str]] = collections.Counter()
seen_values: set[str] = set()
for value in args.expected_source:
if value in seen_values:
raise ValueError(f"duplicate expected source: {value}")
payload = source_payload(root, value, readme_paths)
expected[payload] += 1
labels[payload].append(value)
seen_values.add(value)
for value, readme in readmes:
check_readme(value, readme, expected, labels)
print(f"checked {sum(expected.values())} fenced source blocks in {value}")
except (OSError, UnicodeError, ValueError) as error:
print(error, file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,182 @@
#!/usr/bin/env python3
"""Tests for marker-free tutorial source-block checks."""
from __future__ import annotations
import pathlib
import subprocess
import tempfile
import unittest
SCRIPT = pathlib.Path(__file__).with_name("sync-source-blocks.py")
class SourceBlocksTest(unittest.TestCase):
def setUp(self) -> None:
self.temporary = tempfile.TemporaryDirectory()
self.root = pathlib.Path(self.temporary.name)
(self.root / ".git").mkdir()
(self.root / "src/lesson").mkdir(parents=True)
(self.root / "src/lesson/tool.c").write_text(
"int main(void)\n{\n\treturn 0;\n}\n", encoding="utf-8"
)
(self.root / "src/lesson/contract.h").write_text(
"#define VALUE 1\n", encoding="utf-8"
)
def tearDown(self) -> None:
self.temporary.cleanup()
def write_readme(
self,
name: str,
payload: str | None = None,
language: str = "c",
fence: str = "```",
) -> pathlib.Path:
if payload is None:
payload = (self.root / "src/lesson/tool.c").read_text(encoding="utf-8")
path = self.root / name
path.write_text(
f"# Lesson\n\n{fence}{language}\n{payload}{fence}\n", encoding="utf-8"
)
return path
def write_pair(self, **kwargs: str) -> None:
self.write_readme("src/lesson/README.md", **kwargs)
self.write_readme("src/lesson/README.zh.md", **kwargs)
def run_script(
self,
*arguments: str,
expected: tuple[str, ...] = ("src/lesson/tool.c",),
) -> subprocess.CompletedProcess[str]:
expected_arguments = [
item for source in expected for item in ("--expected-source", source)
]
return subprocess.run(
[
"python3",
str(SCRIPT),
"--repo",
str(self.root),
*expected_arguments,
*arguments,
],
text=True,
capture_output=True,
check=False,
)
def check_pair(self, expected: tuple[str, ...] = ("src/lesson/tool.c",)):
return self.run_script(
"--readme",
"src/lesson/README.md",
"--readme",
"src/lesson/README.zh.md",
"--check",
expected=expected,
)
def test_accepts_exact_standard_markdown_fences(self) -> None:
self.write_pair()
result = self.check_pair()
self.assertEqual(0, result.returncode, result.stderr)
self.assertIn("checked 1 fenced source blocks", result.stdout)
def test_rejects_stale_source_block(self) -> None:
self.write_pair(payload="int stale;\n")
result = self.check_pair()
self.assertEqual(1, result.returncode)
self.assertIn("missing exact fenced source", result.stderr)
def test_rejects_duplicate_exact_source_block(self) -> None:
source = (self.root / "src/lesson/tool.c").read_text(encoding="utf-8")
payload = f"{source}```\n\n```c\n{source}"
self.write_pair(payload=payload)
result = self.check_pair()
self.assertEqual(1, result.returncode)
self.assertIn("duplicate exact fenced source", result.stderr)
def test_rejects_legacy_html_markers(self) -> None:
self.write_pair()
readme = self.root / "src/lesson/README.md"
readme.write_text(
readme.read_text(encoding="utf-8")
+ "<!-- END FULL SOURCE -->\n",
encoding="utf-8",
)
result = self.check_pair()
self.assertEqual(1, result.returncode)
self.assertIn("HTML marker is not allowed", result.stderr)
def test_rejects_source_outside_repository(self) -> None:
self.write_pair()
result = self.check_pair(expected=("../outside.c",))
self.assertEqual(1, result.returncode)
self.assertIn("escapes repository root", result.stderr)
def test_rejects_readme_as_source(self) -> None:
self.write_pair()
result = self.check_pair(expected=("src/lesson/README.md",))
self.assertEqual(1, result.returncode)
self.assertIn("expected source cannot be a tutorial README", result.stderr)
def test_rejects_incomplete_inventory(self) -> None:
self.write_pair()
result = self.check_pair(
expected=("src/lesson/tool.c", "src/lesson/contract.h")
)
self.assertEqual(1, result.returncode)
self.assertIn("src/lesson/contract.h", result.stderr)
def test_rejects_unsupported_source_type(self) -> None:
source = self.root / "src/lesson/source.txt"
source.write_text("text\n", encoding="utf-8")
self.write_pair(payload="text\n", language="text")
result = self.check_pair(expected=("src/lesson/source.txt",))
self.assertEqual(1, result.returncode)
self.assertIn("unsupported expected-source file type", result.stderr)
def test_accepts_longer_fence_for_source_with_backticks(self) -> None:
source = self.root / "src/lesson/tool.py"
payload = 'value = """\n```\n"""\n'
source.write_text(payload, encoding="utf-8")
self.write_pair(payload=payload, language="python", fence="````")
result = self.check_pair(expected=("src/lesson/tool.py",))
self.assertEqual(0, result.returncode, result.stderr)
def test_rejects_empty_source(self) -> None:
(self.root / "src/lesson/tool.c").write_bytes(b"")
self.write_pair(payload="")
result = self.check_pair()
self.assertEqual(1, result.returncode)
self.assertIn("complete source cannot be empty", result.stderr)
def test_rejects_source_without_final_lf(self) -> None:
(self.root / "src/lesson/tool.c").write_bytes(b"int value = 1;")
self.write_pair(payload="int value = 1;\n")
result = self.check_pair()
self.assertEqual(1, result.returncode)
self.assertIn("must end with an LF byte", result.stderr)
def test_requires_bilingual_pair(self) -> None:
self.write_readme("src/lesson/README.md")
result = self.run_script(
"--readme", "src/lesson/README.md", "--check"
)
self.assertEqual(1, result.returncode)
self.assertIn("exactly the English and Chinese README pair", result.stderr)
def test_rejects_unclosed_fence(self) -> None:
self.write_pair()
readme = self.root / "src/lesson/README.zh.md"
readme.write_text("# Lesson\n\n```c\nint value;\n", encoding="utf-8")
result = self.check_pair()
self.assertEqual(1, result.returncode)
self.assertIn("unclosed Markdown code fence", result.stderr)
if __name__ == "__main__":
unittest.main()