feat: gate writer sections on a % teach: block (SP025)
Rewrite references/pedagogy.md around recite-vs-teach: reader model,
coverage density (sections_in is cite permission, not a to-do list),
intuition-before-formula three-beat, and paper-jump filling.
Every notes/sections/sec-*.tex must now answer gap / takeaway / jump /
omit above the first \section. lint.py checks presence only (it cannot
judge honesty); files opening with "% generated by" are exempt.
- scripts/lint.py: SP025 + is_writer_section / teach_gaps helpers
- tests/no-teach-block/: fixture with takeaway only, wired into test.sh
- examples/*: all 14 writer sections get real teach blocks
- SKILL.md, references/{agents,antipatterns,checklist}.md, DESIGN.md,
assets/notes-template.tex: route writers and consistency agent
through pedagogy.md
This commit is contained in:
@@ -43,6 +43,13 @@ KIND_ALIASES = {
|
||||
}
|
||||
STY_RE = re.compile(r"\\usepackage(?:\[[^\]]*\])?\{(superfig|supertensor|superderive)\}")
|
||||
LABEL_RE = re.compile(r"\\splabel\{(C[1-9][0-9]*)\}")
|
||||
SECTION_RE = re.compile(r"^\s*\\section\b", re.MULTILINE)
|
||||
GENERATED_RE = re.compile(r"^\s*%.*generated by", re.IGNORECASE)
|
||||
TEACH_KEYS = ("gap", "takeaway", "jump", "omit")
|
||||
TEACH_RE = {
|
||||
key: re.compile(rf"^%\s*{key}\s*:\s*\S", re.MULTILINE)
|
||||
for key in TEACH_KEYS
|
||||
}
|
||||
|
||||
|
||||
def emit(path: Path, messages: list[str]) -> None:
|
||||
@@ -70,6 +77,19 @@ def collect_ids(data: dict) -> list[str]:
|
||||
return ids
|
||||
|
||||
|
||||
def is_writer_section(path: Path) -> bool:
|
||||
return path.parent.name == "sections" and path.name.startswith("sec-")
|
||||
|
||||
|
||||
def teach_gaps(text: str) -> list[str]:
|
||||
"""Keys missing from the `% teach:` block above the first \\section."""
|
||||
if GENERATED_RE.match(text):
|
||||
return []
|
||||
match = SECTION_RE.search(text)
|
||||
head = text[: match.start()] if match else text
|
||||
return [key for key in TEACH_KEYS if not TEACH_RE[key].search(head)]
|
||||
|
||||
|
||||
def notes_text(notes_paths: list[Path]) -> str:
|
||||
chunks: list[str] = []
|
||||
for path in notes_paths:
|
||||
@@ -155,6 +175,12 @@ def lint(
|
||||
text = path.read_text(encoding="utf-8")
|
||||
if STY_RE.search(text):
|
||||
errors.append(f"SP003 {path.name} loads a figure package")
|
||||
if is_writer_section(path):
|
||||
missing = teach_gaps(text)
|
||||
if missing:
|
||||
errors.append(
|
||||
f"SP025 {path.name} % teach: block missing {', '.join(missing)}"
|
||||
)
|
||||
|
||||
labels = set(LABEL_RE.findall(body)) if notes_scan else set()
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ expect_work "$ROOT/tests/loads-sty" SP003
|
||||
expect_work "$ROOT/tests/missing-request" SP012
|
||||
expect_work "$ROOT/tests/unlabeled-core" SP020
|
||||
expect_work "$ROOT/tests/missing-include" SP021
|
||||
expect_work "$ROOT/tests/no-teach-block" SP025
|
||||
|
||||
echo "==> compile"
|
||||
for dir in \
|
||||
|
||||
Reference in New Issue
Block a user