Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,11 @@ order until it carries the consumer's voice.

### Surface counts (current)

- **27 commands** under `commands/` (plus 2 internal `_translation-pipeline.md`
- **30 commands** under `commands/` (plus 2 internal `_translation-pipeline.md`
/ `_translation-strategy.md` includes — underscore prefix marks them as
helpers reused by `translate-content`).
- **19 agents** under `agents/`.
- **18 skills** under `skills/<skill-name>/SKILL.md`.
- **20 agents** under `agents/`.
- **20 skills** under `skills/<skill-name>/SKILL.md`.
- **1 runtime** under `assets/runtime/overlay-protocol.md` (the executable
spec of the Base + Overlay loop).
- **1 CI workflow** under `.github/workflows/lint.yml` with 3 lint scripts
Expand Down
125 changes: 125 additions & 0 deletions agents/workbook-module-composer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
---
name: workbook-module-composer
description: >
Composes ONE module ("lesson") of an interactive workbook into a self-contained HTML
fragment + manifest, for the `workbook-generate` fan-out (DOJ-4835). Reads a module's
text classes, applies the content-shape -> component mapping, and returns a namespaced
`.wb-module` fragment — NOT a full document. The orchestrator assembles fragments into
the single standalone workbook. Voice-neutral; the shared spec is the consistency
contract.
model: sonnet
tools:
- Read
- Grep
- Glob
---

# Workbook Module Composer Agent

Compose a single module of a course workbook into a **content fragment** the
`workbook-generate` orchestrator can concatenate into one self-contained HTML
file. You handle exactly one module so the whole-course generation can fan out
in parallel without exhausting any one context window.

Read the design system first:
`${CLAUDE_PLUGIN_ROOT}/skills/workbook-generate/SKILL.md` — the component kit,
the content-shape -> component mapping, the accessibility contract, the
"invariant frame vs. creative payload" split, and "the kit is a floor, not a
ceiling". This agent **follows that skill**; it does not redefine it.

## Inputs (passed by the orchestrator)

- `module_index` (required): the module's 1-based position in the course (the
`N` used for the ID namespace and the `mod-{N}` id).
- `module_path` (required): absolute path to the module directory.
- `text_classes` (required): the ordered list of `text-*.md` paths for this
module (already ordered by the orchestrator).
- `module_title` (required): the display title for the module.
- `design` (required): the resolved palette / typography / voice / spacing from
the consumer spec — consume these **only** as the template's CSS custom
properties (e.g. `var(--wb-accent)`). Never hardcode hex values.
- `components` (required): the enabled component catalog (the spec's
`workbooks.components`, or IDT's full built-in catalog when absent).
- `sibling_artifacts` (optional): `quiz-*.md` to fold in as predict-and-reveal
checks; `video-*.md` / `slides-*.html` / `challenge-*.md` to reference (never
embed).

## Process

1. Read the module's `text-*.md` (frontmatter + H1->H2->H3 / lists / tables /
code / prose) and any `quiz-*.md`.
2. Apply the skill's content-shape -> component mapping **as heuristics, not a
lookup table**. Vary the representation to fit each concept; do not default
to the same component for every section. When a concept needs a
visualization the kit lacks, author it directly with inline SVG/CSS within
the design tokens + accessibility contract (the kit is a floor, not a
ceiling). Reserve "compose from the tested kit" for fragile stateful JS.
3. Build the module fragment per the contract below. Every step is one `H2`
(typically); each interactive block must earn its place (felt understanding
or loop-closing export) — no capture-to-nowhere widgets.

## Output contract (return EXACTLY these two blocks)

Return a `MANIFEST` then a `FRAGMENT`, nothing else. Your final message IS the
data the orchestrator parses — no preamble.

**1. `MANIFEST` — a fenced `json` block:**

```json
{
"moduleId": "mod-{N}",
"title": "{module_title}",
"steps": [
{ "id": "m{N}-step-1", "title": "..." },
{ "id": "m{N}-step-2", "title": "..." }
],
"components_used": ["callout", "flow-diagram", "annotated-code", "predict-reveal"]
}
```

**2. `FRAGMENT` — a fenced `html` block** containing a single module chunk:

```html
<div class="wb-module" id="mod-{N}" role="group" aria-label="{module_title}">
<section class="wb-step" id="m{N}-step-1" role="region" aria-label="...">
<div class="wb-chapter"><div class="wb-eyebrow">{module_title}</div></div>
<h2>...</h2>
<!-- kit components / bespoke inline SVG, all theme-variable-driven -->
</section>
<!-- more steps... -->
</div>
```

## Fragment rules (non-negotiable — the assembler depends on them)

- **ID & attribute namespacing:** every `id`, `aria-controls`,
`aria-labelledby`, label `for`, and form-control `name` (radio/checkbox
groups) you emit is prefixed `m{N}-` (e.g. `m3-acc-1-panel`,
`name="m3-quiz-1"`). This guarantees global uniqueness — and correct
label/control associations and radio-group isolation — when fragments are
concatenated. The module wrapper id is `mod-{N}`.
- **Fragment only — no global chrome.** Emit the single `.wb-module` div and its
`.wb-step` sections. Do **NOT** emit `<html>`, `<head>`, `<style>`,
`<script>`, the `.wb-progress` bar, the `.wb-nav` table of contents, or the
`.wb-module__nav` footer. Those are global and the orchestrator owns them
(the footer is added by the assembler so the "Module N of M" arc is correct).
- **No hardcoded brand.** Use the CSS custom properties (`var(--wb-accent)`,
`var(--wb-text)`, `var(--wb-font-body)`, etc.) — never literal hex or font
names. Brand lives in the assembled document's `:root`.
- **Accessibility:** each step is `role="region"` with an `aria-label`;
interactive controls are focusable; quiz/diff/comparison state is conveyed by
text + icon, not color alone; honour `prefers-reduced-motion` patterns from
the kit.
- **In-memory only / standalone:** no `localStorage`, no `postMessage`, no CDN
runtime deps. Any image is inlined or relative.
- **Voice-neutral:** no brand voice, no "Dojo"-isms, no momentum copy. Leave the
forward-hook teaser to the overlay (the assembler/overlay owns it).

## Anti-patterns

- Emitting a full HTML document, a `<style>`/`<script>` block, the nav, the
progress bar, or the module footer — all assembler-owned.
- Un-namespaced ids (collide on concatenation).
- Hardcoded colors/fonts instead of the design tokens.
- Mapping every section to the same component (monotony) — vary to fit.
- Capture-to-nowhere widgets; freeform stateful JS.
38 changes: 36 additions & 2 deletions commands/workbook-generate.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,36 @@ and `workbooks.step_patterns`. Read:
6. Read each reading's frontmatter (`title`, `order`, `duration_min`, `module`,
`course`, `tags`) and body (H1 -> H2 -> H3, lists, tables, code, prose).

## Phase 2.5 — Execution strategy (linear vs. per-module fan-out)

Choose how to compose the workbook based on course size:

- **Small course** (≈ 2 modules or fewer, or little total reading) — compose
**linearly** in this session (Phases 3–4 inline). Simplest; no drift risk.
- **Larger course** — **fan out**: dispatch one
`${CLAUDE_PLUGIN_ROOT}/agents/workbook-module-composer.md` subagent **per
module, in parallel** (via the standard Task/Agent tool). This keeps each
agent's context focused on one module, handles big courses without exhausting
the window, and tends to vary visualizations across modules. This uses
ordinary subagents — **NOT** any session-specific orchestration tool — so the
command stays portable across consumer installs.

When fanning out, pass each composer: `module_index` (its 1-based position, used
as the `m{N}-` ID namespace), `module_path`, the ordered `text_classes` list,
`module_title`, the resolved `design` tokens + enabled `components`, and any
`sibling_artifacts`. Each returns a **MANIFEST + a namespaced `.wb-module`
FRAGMENT** per that agent's output contract — not a full HTML. Collect all
fragments + manifests for Phase 4 assembly.

Both paths produce the same thing — composed module chunks. Phase 3 is the
composition ruleset (applied inline when linear, or by each composer when fanned
out); Phase 4 assembles.

## Phase 3 — Map content to components

Apply the **content-shape -> component mapping** from the skill. Summary:
These rules govern composing **each module's chunk** — applied inline (linear)
or by each `workbook-module-composer` subagent (fan-out). Apply the
**content-shape -> component mapping** from the skill. Summary:

- Course -> module **chapters** + a **table of contents** + course **progress**.
- Each reading -> a run of steps in its module chapter; each `H2` -> a **step**.
Expand Down Expand Up @@ -119,7 +146,14 @@ payload" and "The kit is a floor, not a ceiling".
properties (`:root` variables). Under graceful-degrade, keep the neutral
defaults already in the template.
3. Replace the content region between the WORKBOOK CONTENT markers with the
composed module chapters, steps, and components from Phase 3.
composed module chunks, in module order. **If you fanned out (Phase 2.5):**
drop in each composer's `FRAGMENT` verbatim, ordered by `module_index`;
verify every `id` is `m{N}-`-namespaced and **globally unique** across

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[NIT] ⚪ P4 (nit) — Escaped quotes (\"Module N of M\") are used within the parentheses, which will display the backslashes literally in markdown editors. Standard quotes can be used directly here.

fragments (fix or re-request any collision); the composers deliberately omit
the TOC, progress bar, and module footers — **you own those** and build them
here (the omitted module footer is why the "Module N of M" arc stays correct
at assembly). Build the `.wb-nav` table of contents from the `MANIFEST`s
(module links `#mod-{N}` + step links `#m{N}-step-{k}`).
4. Honour `workbooks.scroll_behavior` — `doc` (navigable, module-chunked;
**default**) or `stepped` (one screen at a time) — and `workbooks.animation`
(enter transition + `prefers-reduced-motion`). In `doc` mode the TOC is a
Expand Down
42 changes: 42 additions & 0 deletions skills/workbook-generate/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,48 @@ visualization is encouraged. The only thing to avoid is fragile, untested
**stateful JS** widgets — those should be added to the kit deliberately (and
tested), not improvised per page.

## Generation at scale: per-module fan-out (orchestration)

A per-course workbook can span many modules × text classes — more than one
context window composes well in a single pass. So generation is **size-gated**:

- **Small course** (≈ 2 modules or fewer) → compose **linearly** in one pass.
- **Larger course** → **map → reduce**: dispatch one `workbook-module-composer`
subagent **per module** (in parallel, via the standard Task/Agent tool — never
a session-specific orchestration tool; the command must stay portable), then
the orchestrator **assembles** the returned fragments into the single
self-contained workbook.

This is also a quiet win for variety: independent per-module composition resists
the "I already used a chart, reuse it" monotony a single pass can fall into.

### The fragment contract (what binds it together)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[NIT] ⚪ P4 (nit) — Escaped quotes (\"wb-module\") are used in the markdown backticks, which will render literally with backslashes on some markdown parsers. Removing the backslashes improves formatting readability.

Each composer returns a **MANIFEST** (`{ moduleId, title, steps[], components_used }`)
plus a **FRAGMENT**: one `<div class="wb-module" id="mod-{N}">` of namespaced
`.wb-step`s and kit components — **not** a full document. Rules:

- **ID & attribute namespacing:** every `id` / `aria-controls` /
`aria-labelledby` / label `for` / form-control `name` is prefixed `m{N}-`
(module index) so fragments concatenate without id collisions, broken label
associations, or cross-module radio-group bleed.
- **Fragment only:** no `<html>/<head>/<style>/<script>`, no `.wb-progress`, no
`.wb-nav`, **no `.wb-module__nav` footer**. Those are global and the
orchestrator owns them — the footer is added at assembly so the "Module N of M"
arc is correct.
- **No hardcoded brand:** consume design tokens (`var(--wb-accent)`, …), never
literal hex/fonts. Voice-neutral; the forward-hook teaser stays an overlay
slot.

### Why it stays consistent (no stitched-together feel)

The shared `instruction-bundle-spec.yaml` (brand / a11y / kit) is the binding
contract handed to every composer, so fragments are visually uniform by
construction. The **assembler** then runs a normalization pass — verify each
fragment uses only allowed components, IDs are namespaced + globally unique,
a11y roles are present — and runs the variety/fit self-check on the assembled
whole. Drift is bounded by the frame, not left to chance.

## Consuming the consumer spec (`instruction-bundle-spec.yaml`)

The workbook's brand + structural vocabulary comes from the consumer's
Expand Down
Loading