Skip to content

Commit 6c2ccba

Browse files
apartsinclaude
andcommitted
Agent skills: self-contained reuse package with CSS, icons, templates, scripts, and audit framework
Bundle all reusable assets into agents/book-skills/ for portability to new book projects: - styles/book.css (annotated with 43 SECTION markers) + 17 callout icons - HTML templates (section, chapter-index, part-index page skeletons) - generate_icons_gemini.py with Gemini native image gen + batch API (50% discount) - 69 audit check modules, 13 fix scripts, 4 detect scripts - Detailed ToC CSS: flex layout, border-left grouping, aligned sections, muted links - Audit tuning: p3_math_rendering.py and p1_stacked_captions.py reduced to 0 false positives Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b495007 commit 6c2ccba

134 files changed

Lines changed: 16243 additions & 115 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

agents/book-skills/SKILL.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,33 @@ the new project's root directory:
3333
Agents read these files at runtime. The skill definition itself contains only generic
3434
pipeline logic, agent roles, and quality rules that apply to any textbook.
3535

36+
### Reusable Assets (copy to new book projects)
37+
38+
The callout system, icons, CSS, templates, and scripts are bundled within this skill directory for reuse across books.
39+
40+
**Included in this skill directory (ships with the skill):**
41+
42+
| Asset | Path (relative to `agents/book-skills/`) | What it contains |
43+
|-------|------------------------------------------|------------------|
44+
| **Book stylesheet** | `styles/book.css` | All layout, callout types, code output panes, typography, tables (annotated with 43 `SECTION:` markers) |
45+
| **Callout icons** | `styles/icons/callout-*.png` and `*.svg` | 15 callout type icons (48x48) |
46+
| **HTML templates** | `templates/section-template.html`, `chapter-index-template.html`, `part-index-template.html` | Page skeletons with placeholder markup |
47+
| **Chapter status** | `templates/chapter-status-template.md` | Per-chapter audit status tracker |
48+
| **Icon generator** | `scripts/generate_icons_gemini.py` | Batch icon generation via Gemini API (Imagen, Gemini native, batch mode with 50% discount) |
49+
50+
| **Audit framework** | `scripts/audit/run.py` + `scripts/audit/checks/*.py` | 69 automated QA check modules with plugin runner |
51+
| **Fix scripts** | `scripts/fix/*.py` | 13 reusable HTML fix scripts (accessibility, code blocks, math, captions, etc.) |
52+
| **Detect scripts** | `scripts/detect/*.py` | 4 standalone audit scripts (HTML quality, SVG, print contrast, format validation) |
53+
54+
**Referenced from the project (copy when starting a new book):**
55+
56+
| Asset | Project Path | What it contains |
57+
|-------|-------------|------------------|
58+
| KaTeX vendor | `vendor/katex/` | Math rendering (KaTeX CSS + JS + auto-render) |
59+
| Prism vendor | `vendor/prism/` | Syntax highlighting for code blocks |
60+
61+
The callout system supports 15 types: `big-picture`, `key-insight`, `note`, `warning`, `practical-example`, `fun-note`, `research-frontier`, `algorithm`, `tip`, `exercise`, `key-takeaway`, `library-shortcut`, `pathway`, `self-check`, `lab`. Each type has CSS (colors, borders, gradients), a `::before` icon, and a `::after` tooltip, all driven by the class name alone. See agent #25 (Visual Identity Director) for the full catalog.
62+
3663
**Note:** The book structure may change over time (Parts renumbered, chapters added or
3764
moved). `BOOK_CONFIG.md` is the single source of truth for the current structure. All
3865
agents that reference chapter numbers, Part names, or cross-references MUST read

agents/book-skills/agents/19-structural-architect.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,13 @@ See the full ordering in "Standard Element Ordering" below. Within `<main class=
126126

127127
### Callout Type Catalog
128128

129-
The book uses 15 callout types, each with a dedicated SVG/PNG icon and a CSS tooltip (shown on hover). Icons and tooltips are defined centrally in `book.css` via `::before` and `::after` pseudo-elements. No HTML changes are needed for icons or tooltips; they are applied automatically by class name.
129+
The book uses 15 callout types, each with a dedicated SVG/PNG icon and a CSS tooltip (shown on hover). All definitions live in these files (read them for authoritative values):
130+
131+
- **CSS definitions**: `styles/book.css` (callout base ~830, variants ~870-1115, icons ~1025-1055, tooltips ~895-920)
132+
- **Icon files**: `styles/icons/callout-{type}.png` or `callout-{type}.svg` (48x48)
133+
- **Legacy icon set** (not used by book.css): `images/icons/` with separate `icons.css`
134+
135+
No HTML changes are needed for icons or tooltips; they are applied automatically by class name.
130136

131137
| Class | Icon | Color | Purpose |
132138
|-------|------|-------|---------|

agents/book-skills/agents/25-visual-identity-director.md

Lines changed: 59 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -45,66 +45,65 @@ Apply approved visual identity fixes directly into chapter HTML. Replace inline
4545

4646
All canonical CSS is defined in the shared stylesheet `styles/book.css`, which is the single source of truth for all visual styling. Files should link to this stylesheet via `<link rel="stylesheet" href="../../styles/book.css">` instead of embedding inline CSS. When auditing visual identity, verify that files link to the shared stylesheet and do not override its definitions with conflicting inline styles.
4747

48-
The key definitions in `styles/book.css` include:
49-
50-
### Epigraph
51-
```css
52-
.epigraph {
53-
max-width: 600px;
54-
margin: 2rem auto 2.5rem;
55-
padding: 1.2rem 1.5rem;
56-
border-left: 4px solid var(--highlight, #e94560);
57-
background: linear-gradient(135deg, rgba(233,69,96,0.04), rgba(15,52,96,0.04));
58-
border-radius: 0 8px 8px 0;
59-
font-style: italic;
60-
font-size: 1.05rem;
61-
line-height: 1.6;
62-
color: var(--text, #1a1a2e);
63-
}
64-
.epigraph p { margin: 0 0 0.5rem 0; }
65-
.epigraph cite {
66-
display: block;
67-
text-align: right;
68-
font-style: normal;
69-
font-size: 0.9rem;
70-
color: var(--highlight, #e94560);
71-
font-weight: 600;
72-
}
73-
.epigraph cite::before { content: "\2014\00a0"; }
48+
**Do NOT duplicate CSS in agent skills.** Always read `styles/book.css` directly for the authoritative definitions. The key sections in `book.css` are:
49+
50+
| Section | Approximate Lines | What it defines |
51+
|---------|-------------------|-----------------|
52+
| Epigraph | ~50-80 | `.epigraph` styling, max-width 600px, cite formatting |
53+
| Callout base | ~830-870 | `.callout` shared styling, tooltip `::after`, icon `::before` |
54+
| Callout variants | ~870-1115 | All 15 callout type colors, borders, backgrounds |
55+
| Callout icons | ~1025-1055 | `::before` background-image rules pointing to `styles/icons/` |
56+
| Code output | ~1380-1420 | `.code-output` green border, "Output" label |
57+
| Prerequisites | ~220-250 | `.prerequisites` max-width 600px |
58+
59+
### Callout System (15 canonical types)
60+
61+
The book uses 15 callout types. All styling, icons, and tooltips are defined centrally in `styles/book.css`. Icons live in `styles/icons/` as `callout-{type}.png` or `callout-{type}.svg`. No inline styles or per-file overrides are needed.
62+
63+
**File locations:**
64+
- CSS definitions: `styles/book.css` (lines ~870-1115)
65+
- Icon files: `styles/icons/callout-{type}.png` or `.svg`
66+
- Legacy icon set (unused): `images/icons/` with separate `icons.css`
67+
68+
| # | Class | Border Color | Title Color | Icon File | Purpose |
69+
|---|-------|-------------|-------------|-----------|---------|
70+
| 1 | `big-picture` | #7c3aed (purple) | #7c3aed | callout-big-picture.png | Why this topic matters; once near top |
71+
| 2 | `key-insight` | #43a047 (green) | #2e7d32 | callout-key-insight.png | Core concept worth remembering |
72+
| 3 | `note` | #1976d2 (blue) | #1565c0 | callout-note.png | Supplementary detail or clarification |
73+
| 4 | `warning` | #f9a825 (amber) | #e65100 | callout-warning.png | Common mistakes or pitfalls |
74+
| 5 | `practical-example` | #5dade2 (grey-blue) | #2980b9 | callout-practical-example.png | Real-world production scenario |
75+
| 6 | `fun-note` | #e91e63 (pink) | #c2185b | callout-fun-note.png | Lighthearted or surprising fact |
76+
| 7 | `research-frontier` | #00897b (teal) | #00796b | callout-research-frontier.png | Active research directions |
77+
| 8 | `algorithm` | #4a55a2 (indigo) | #2e3990 | callout-algorithm.png | Step-by-step pseudocode |
78+
| 9 | `tip` | #00acc1 (cyan) | #006064 | callout-tip.png | Practical shortcut or best practice |
79+
| 10 | `exercise` | #e64a19 (deep orange) | #c62828 | callout-exercise.png | Hands-on exercise with solution |
80+
| 11 | `key-takeaway` | #f9a825 (gold) | #f57f17 | callout-key-takeaway.svg | Essential takeaway to remember |
81+
| 12 | `library-shortcut` | #00897b (teal) | #00695c | callout-library-shortcut.svg | Library that solves task in fewer lines |
82+
| 13 | `pathway` | #7e57c2 (purple) | #5e35b1 | callout-pathway.svg | Recommended learning path |
83+
| 14 | `self-check` | #3949ab (indigo) | #283593 | callout-self-check.svg | Quick comprehension quiz |
84+
| 15 | `lab` | #00897b (teal-green) | #00695c | callout-lab.svg | Guided hands-on lab exercise |
85+
86+
**Callout HTML template:**
87+
```html
88+
<div class="callout {type}">
89+
<div class="callout-title">{Title Text}</div>
90+
<p>Content here.</p>
91+
</div>
7492
```
7593

76-
### Callout Boxes (10 canonical types)
77-
```css
78-
.callout.big-picture { background: linear-gradient(135deg,#f3e8ff,#e8f4f8); border-left: 4px solid #7c3aed; }
79-
.callout.big-picture .callout-title { color: #7c3aed; }
94+
**Rules:**
95+
- Every callout MUST use one of these 15 classes. Do not invent new types.
96+
- The class name alone controls icon, color, border, and tooltip. Never add inline styles.
97+
- Exercise callouts use `<details><summary>` for collapsible solutions.
98+
- Self-check callouts use `.quiz-question` for question text.
99+
- Algorithm callouts use `<pre>` inside the callout for pseudocode, with `.algo-line-keyword` and `.algo-line-comment` spans.
80100

81-
.callout.key-insight { background: linear-gradient(135deg,#e8f5e9,#f1f8e9); border-left: 4px solid #43a047; }
82-
.callout.key-insight .callout-title { color: #2e7d32; }
83-
84-
.callout.note { background: linear-gradient(135deg,#e3f2fd,#f3e8ff); border-left: 4px solid #1976d2; }
85-
.callout.note .callout-title { color: #1565c0; }
86-
87-
.callout.warning { background: linear-gradient(135deg,#fff8e1,#fff3e0); border-left: 4px solid #f9a825; }
88-
.callout.warning .callout-title { color: #e65100; }
89-
90-
.callout.practical-example { background: #f5f5f5; border: 1px solid #e0e0e0; border-left: 5px solid #5dade2; border-radius: 0 8px 8px 0; }
91-
.callout.practical-example .callout-title { color: #2980b9; }
92-
93-
.callout.fun-note { background: linear-gradient(135deg,#fce4ec,#f3e5f5); border-left: 4px solid #e91e63; }
94-
.callout.fun-note .callout-title { color: #c2185b; }
95-
96-
.callout.research-frontier { background: linear-gradient(135deg, #e0f2f1, #e0f7fa); border-left-color: #00897b; }
97-
.callout.research-frontier .callout-title { color: #00796b; }
98-
99-
.callout.algorithm { background: linear-gradient(135deg, #ede7f6, #e8eaf6); border-left-color: #5c6bc0; }
100-
.callout.algorithm .callout-title { color: #3949ab; }
101-
102-
.callout.tip { background: linear-gradient(135deg, #e0f7fa, #e1f5fe); border-left-color: #00acc1; }
103-
.callout.tip .callout-title { color: #00838f; }
104-
105-
.callout.exercise { background: linear-gradient(135deg, #fbe9e7, #fff3e0); border-left-color: #e64a19; }
106-
.callout.exercise .callout-title { color: #bf360c; }
107-
```
101+
**Adding a new callout type (for future books):**
102+
1. Add CSS block in `book.css`: background gradient, border-color, title color
103+
2. Add tooltip in `book.css`: `.callout.{type} .callout-title::after { content: "..."; }`
104+
3. Add icon rule: `.callout.{type} .callout-title::before { background-image: url('icons/callout-{type}.png'); }`
105+
4. Create icon file: `styles/icons/callout-{type}.png` (48x48, matching existing style)
106+
5. Update agent #19 callout catalog and agent #25 callout table
108107

109108
### Prerequisites Box
110109
```css
@@ -351,7 +350,7 @@ If it matches exactly, skip the file. Only edit files with actual deviations.
351350

352351
### Execution Checklist
353352
- [ ] Verified every section file links to the shared stylesheet (`styles/book.css`)
354-
- [ ] Checked all 9 canonical callout types use correct CSS class names and styling
353+
- [ ] Checked all 15 canonical callout types use correct CSS class names and styling
355354
- [ ] Confirmed no inline `style=` attributes exist on elements that should use CSS classes
356355
- [ ] Verified element ordering in every section file (epigraph, prerequisites, content, research frontier, what's next, bibliography)
357356
- [ ] Checked that no rogue color schemes or unauthorized CSS overrides appear
@@ -362,7 +361,7 @@ If it matches exactly, skip the file. Only edit files with actual deviations.
362361
### Pass/Fail Checks
363362
- [ ] No section file is missing the shared stylesheet link
364363
- [ ] No element in the list (`.whats-next`, `.epigraph`, `.prerequisites`, `.callout`, `.bibliography`, `.code-caption`, `.lab`, `.diagram-container`) has an inline `style=` attribute
365-
- [ ] Every callout box uses one of the 9 canonical classes: `big-picture`, `key-insight`, `note`, `warning`, `practical-example`, `fun-note`, `research-frontier`, `algorithm`, `tip`
364+
- [ ] Every callout box uses one of the 15 canonical classes: `big-picture`, `key-insight`, `note`, `warning`, `practical-example`, `fun-note`, `research-frontier`, `algorithm`, `tip`, `exercise`, `key-takeaway`, `library-shortcut`, `pathway`, `self-check`, `lab`
366365
- [ ] No CSS property in any file's `<style>` block contradicts the canonical definitions in `book.css`
367366
- [ ] Width rules are correct: `.epigraph` and `.prerequisites` at 600px; all other recurring elements at full content width (no narrower max-width)
368367
- [ ] No em dashes or double dashes appear in any added text
@@ -382,7 +381,7 @@ If it matches exactly, skip the file. Only edit files with actual deviations.
382381
### What the Meta Agent Checks
383382
- Glob all section HTML files in scope; verify each contains a `<link>` to `styles/book.css` (or the correct relative path)
384383
- Search for `style=` attributes on recurring elements (`.whats-next`, `.epigraph`, `.prerequisites`, `.callout`, `.bibliography`, `.code-caption`, `.lab`, `.diagram-container`); count must be 0
385-
- Extract all callout class names from `class="callout ..."` patterns; verify each secondary class is in the canonical list (`big-picture`, `key-insight`, `note`, `warning`, `practical-example`, `fun-note`, `research-frontier`, `algorithm`, `tip`)
384+
- Extract all callout class names from `class="callout ..."` patterns; verify each secondary class is in the canonical list (`big-picture`, `key-insight`, `note`, `warning`, `practical-example`, `fun-note`, `research-frontier`, `algorithm`, `tip`, `exercise`, `key-takeaway`, `library-shortcut`, `pathway`, `self-check`, `lab`)
386385
- Compare CSS properties in each file's `<style>` block against canonical definitions; flag any contradictions (different background, border-left-color, padding, etc.)
387386
- Check `max-width` values: `.epigraph` and `.prerequisites` should be 600px; `.whats-next`, `.bibliography`, `.callout`, `.lab` should NOT have a narrower max-width than `.content`
388387

agents/book-skills/scripts/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22

33
## Included (generalizable to any HTML textbook)
44

5+
### generate_icons_gemini.py
6+
Batch icon generation via Gemini Imagen and Gemini native image APIs.
7+
Supports parallel single calls (Imagen) and batch API with 50% discount (Gemini).
8+
9+
Usage:
10+
```bash
11+
python generate_icons_gemini.py --list # show available types
12+
python generate_icons_gemini.py --engine gemini --batch # batch mode (50% off)
13+
python generate_icons_gemini.py --types exercise,tip # specific types only
14+
```
15+
16+
Requires `GEMINI_API_KEY` environment variable or `.env.all` file in book root.
17+
518
### audit/
619
Plugin-based HTML quality audit framework. Each check is a standalone Python
720
module with a `run(filepath, html, context)` signature. The runner discovers

agents/book-skills/scripts/audit/__init__.py

Whitespace-only changes.

agents/book-skills/scripts/audit/checks/__init__.py

Whitespace-only changes.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""Check for broken cross-reference links (relative hrefs to nonexistent files)."""
2+
import re
3+
from pathlib import Path
4+
from collections import namedtuple
5+
6+
PRIORITY = "P0"
7+
CHECK_ID = "BROKEN_XREF"
8+
DESCRIPTION = "Relative href points to a file that does not exist on disk"
9+
10+
Issue = namedtuple("Issue", ["priority", "check_id", "filepath", "line", "message"])
11+
12+
HREF_RE = re.compile(r'href="([^"]+)"')
13+
SKIP_PREFIXES = ("http://", "https://", "mailto:", "javascript:", "tel:", "#")
14+
15+
16+
def run(filepath, html, context):
17+
issues = []
18+
all_files = context["all_files"]
19+
for i, line in enumerate(html.split("\n"), 1):
20+
for m in HREF_RE.finditer(line):
21+
href = m.group(1)
22+
if any(href.startswith(p) for p in SKIP_PREFIXES):
23+
continue
24+
# Strip fragment
25+
clean = href.split("#")[0]
26+
if not clean:
27+
continue
28+
# Resolve relative path
29+
target = (filepath.parent / clean).resolve()
30+
if target not in all_files and not target.exists():
31+
issues.append(Issue(PRIORITY, CHECK_ID, filepath, i,
32+
f'Broken link: href="{href}"'))
33+
return issues
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""Check for duplicate figure, table, or listing numbers within a single file.
2+
3+
Only counts *caption-bearing* elements (figcaption, code-caption,
4+
diagram-caption, table caption). Prose cross-references and aria-labels
5+
are expected to repeat the same number and are therefore excluded.
6+
"""
7+
import re
8+
from collections import defaultdict, namedtuple
9+
10+
PRIORITY = "P0"
11+
CHECK_ID = "DUP_FIGURE_NUM"
12+
DESCRIPTION = "Same Figure/Table/Listing number used multiple times in one file"
13+
14+
Issue = namedtuple("Issue", ["priority", "check_id", "filepath", "line", "message"])
15+
16+
NUM_RE = re.compile(r'(?:Figure|Table|Listing|Code Fragment)\s+(\d+\.\d+(?:\.\d+)?)')
17+
18+
_CAPTION_MARKERS = ('figcaption', 'code-caption', 'diagram-caption', '<caption')
19+
20+
21+
def _is_caption_line(line: str) -> bool:
22+
low = line.lower()
23+
return any(m in low for m in _CAPTION_MARKERS)
24+
25+
26+
def run(filepath, html, context):
27+
issues = []
28+
occurrences = defaultdict(list)
29+
for i, line in enumerate(html.split("\n"), 1):
30+
if not _is_caption_line(line):
31+
continue
32+
for m in NUM_RE.finditer(line):
33+
occurrences[m.group(0)].append(i)
34+
for label, lines in sorted(occurrences.items()):
35+
if len(lines) > 1:
36+
issues.append(Issue(PRIORITY, CHECK_ID, filepath, lines[0],
37+
f'Duplicate "{label}" on lines: {", ".join(str(l) for l in lines)}'))
38+
return issues
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""Check for redundant title text inside SVG diagrams (duplicates the caption below)."""
2+
import re
3+
from collections import namedtuple
4+
5+
PRIORITY = "P0"
6+
CHECK_ID = "SVG_TITLE_TEXT"
7+
DESCRIPTION = "SVG contains a title-like <text> element that duplicates the external caption"
8+
9+
Issue = namedtuple("Issue", ["priority", "check_id", "filepath", "line", "message"])
10+
11+
# Match <text ... y="N" ... font-size="N" ... font-weight="bold" ...>long text</text>
12+
TEXT_RE = re.compile(
13+
r'<text\b([^>]*)>([^<]{10,})</text>', re.IGNORECASE
14+
)
15+
Y_RE = re.compile(r'\by=["\'](\d+(?:\.\d+)?)["\']')
16+
FSIZE_RE = re.compile(r'\bfont-size=["\'](\d+(?:\.\d+)?)["\']')
17+
BOLD_RE = re.compile(r'\bfont-weight=["\'](?:bold|[67]00)["\']')
18+
19+
20+
def _is_inside_svg(html, pos):
21+
"""Check if position is inside an <svg> block."""
22+
before = html[:pos]
23+
last_open = before.rfind("<svg")
24+
last_close = before.rfind("</svg>")
25+
return last_open > last_close
26+
27+
28+
def run(filepath, html, context):
29+
issues = []
30+
lines = html.split("\n")
31+
for i, line_text in enumerate(lines, 1):
32+
for m in TEXT_RE.finditer(line_text):
33+
attrs = m.group(1)
34+
text_content = m.group(2).strip()
35+
36+
# Must be bold or large font
37+
y_match = Y_RE.search(attrs)
38+
fsize_match = FSIZE_RE.search(attrs)
39+
is_bold = bool(BOLD_RE.search(attrs))
40+
41+
if not y_match or not fsize_match:
42+
continue
43+
44+
y_val = float(y_match.group(1))
45+
fsize_val = float(fsize_match.group(1))
46+
47+
# Title criteria: near top (y <= 45), large font (>= 13), bold
48+
if y_val <= 45 and fsize_val >= 13 and is_bold:
49+
# Check word count (titles have 3+ words)
50+
words = text_content.split()
51+
if len(words) >= 3:
52+
# Verify inside SVG
53+
line_start = sum(len(lines[j]) + 1 for j in range(i - 1))
54+
if _is_inside_svg(html, line_start):
55+
display = text_content[:60]
56+
issues.append(Issue(PRIORITY, CHECK_ID, filepath, i,
57+
f'SVG title text (redundant with caption): "{display}"'))
58+
return issues

0 commit comments

Comments
 (0)