---
name: alignment
description: "Validate CLO → MLO → assessment → material alignment across a Canvas course. Two modes: Mode A Classic Report (timestamped HTML + remediation brief), Mode B Graph Preview (typed graph.json + static HTML preview + KEEP/ADD/REPLACE/OPTIONAL mockup decisions, with opt-in --include-canvas extractor)."
---

# Alignment

> **HPI/ID standard** — this skill applies the HPI/ID practitioner standard automatically (CLAUDE.md §Operating Standard: HPI/ID Practitioner). Ground decisions in `standards/instructional-design.md` — design sections 1–12 and the HPI diagnostic layer in Section 13. Name the framework behind every judgment; every design recommendation carries gap → data → principle → mechanism.

> **Invocation gate (no unsolicited reports)** — only generate an alignment report (HTML, `graph.json`, `alignment-preview.html`, or any equivalent artifact) when (a) the user explicitly asks for one, or (b) you ask and the user says yes. Do not auto-run the alignment pipeline as a side effect of related work (audits, mockups, builds, content edits). Discussing alignment, surfacing gaps in chat, or sketching coverage inline is fine; writing a persistent report artifact requires user opt-in.


## Purpose

Validate CLO -> MLO -> assessment -> material alignment across a Canvas course using a read-only, evidence-anchored extraction and report workflow.

## When to Use

This skill ships in two modes. Pick based on the user's prompt.

**Mode A — Classic Report** (default, existing pipeline). Trigger phrases:

- "is my course aligned"
- "check CLO coverage"
- "do my assessments map to objectives"
- "alignment audit"
- "alignment check"
- "alignment report"

**Mode B — Graph Preview** (opt-in, new pipeline from v7.13.0+v7.14.0). Trigger phrases:

- "alignment graph"
- "alignment preview" (qualified — bare "preview" remains owned by `course-mockup`)
- "show alignment paths"
- "alignment with canvas evidence"
- "alignment for mockups"
- "KEEP/ADD/REPLACE/OPTIONAL labels"
- "mockup decisions"

**Ambiguity resolution.** If the user's prompt does not clearly map to Mode A or Mode B (e.g. "run alignment", "do alignment"), ask once: "Classic audit report (Mode A) or graph preview with confidence labels and mockup decisions (Mode B)?" Default-on-no-answer is Mode A. Never run both modes silently.

**Boundary phrases.** Any phrase that does not appear in the Mode A or Mode B trigger lists above routes through the ambiguity-resolution rule. Common boundary phrases that should trigger the ask-once branch include: "visualize alignment", "alignment evidence", "show me how aligned things are", "alignment overview", "alignment status", "alignment view". When in doubt, ask — never silently pick a mode based on inference. The catch-all keeps the routing predictable when a new phrasing emerges that has not been added to either trigger list yet.

## Family

Review, sibling to `course-audit`.

## Inputs

- Course ID

## Mode A — Classic Report

### Outputs

- HTML report at `courses/{id}/reports/{YYYYMMDD-HHmmss}-alignment.html`
- Remediation brief at `courses/{id}/staging/alignment/remediation_brief.json` with `criteria_to_remediate[]` for `bulk-edit` Remediation Brief Mode

### Workflow

1. Run `python3 scripts/alignment_skill.py --course-id {id} --phase scrape` -> produces `extract_manifest.json` with raw HTML and a regex pre-pass.
2. Review `extracted_clos_regex_pass` and `extracted_mlos_by_module_regex_pass`. Regex extracts explicit syllabus CLO sections and module overview `Learning Objectives` / `Module Objectives` / `Module Outcomes` lists.
3. Use Claude AI fallback only for gaps: fill `extracted_clos` if the regex CLO pass is empty, and fill `extracted_mlos_by_module` only for modules classified `topics_only`, `prose_only`, or `empty`. Each fallback item must be verbatim-quoted from source. Mark confidence: `high` if anchor clean, confidence: `low` if interpretation needed.
4. Run `python3 scripts/alignment_skill.py --course-id {id} --phase report` -> merges regex + fallback extractions, builds graph, generates HTML report, and writes `remediation_brief.json`.

## Mode B — Graph Preview

### Outputs

- Typed graph artifact at `courses/{id}/alignment/graph.json` (schema `alignment-graph-preview/v1`)
- Static HTML preview at `courses/{id}/alignment/alignment-preview.html` (self-contained, no external deps)
- Optional chat-inferred mappings sidecar at `courses/{id}/alignment/inferred_mappings.json` (schema `alignment-inferred-mappings/v1`) — populated by Claude during thought-partner conversations; read automatically on every Mode B build; persists across sessions; never overwrites canonical `course-config.json` fields
- Mockup decision artifact at `courses/{id}/alignment/mockup_decisions.json` (schema `alignment-mockup-decisions/v1`) — **separate concern**: only when the user is working on quiz/assignment/discussion mockups. NOT part of the alignment-review thought-partner workflow below.

### Thought-partner workflow (alignment review)

When the user prompt is alignment-review-flavored — "review alignment for {course}", "review the alignment", "is this aligned?", "check CLO/MLO mapping", "review CLOs and MLOs" — Claude is the ID's thought partner, not a script runner.

Canvas rarely declares curriculum ladders (CLO↔MLO mappings, assessment→MLO mappings). The ID expects Claude to **infer** those mappings, give a verdict in chat, and discuss back-and-forth. Reports are post-hoc snapshots that visualize the inferred alignment state — the ID uses them to guide a conversation with faculty (email / zoom / slack / whatever). The ID never hands the report to faculty directly.

**Workflow steps:**

1. Extract data — Canvas (existing Mode B `--include-canvas` path) for Scenario 1 existing courses, OR parse the provided planning doc (paste / PDF / DOCX via existing `course-build` / `syllabus-generator` extractors) for Scenario 2 new courses.
2. Read `courses/{id}/alignment/inferred_mappings.json` if present — carry forward prior chat-session inferences as the starting point.
3. Reason about alignment in chat. Cite verbatim source quotes (mirrors Mode A's Evidence Anchoring discipline). Provide gap → data → principle → mechanism per the §HPI/ID standard.
4. Give verdict + propose ladders. Discuss with the ID. Iterate.
5. After each agreement, write the updated mappings to `courses/{id}/alignment/inferred_mappings.json` (schema below). This persists across sessions so the ID can resume tomorrow without re-deriving.
6. (Optional) ID asks for a report. Run `python3 scripts/alignment_mode_b.py --course-id {id}` — orchestrator auto-loads the sidecar and renders the graph with inferred mappings as `INFERRED` edges ("Likely alignment" badge, dashed line).
7. (Optional, later) ID approves with the **exact phrase** `alignment approved` / `faculty approved` / `align approved`. Ambiguous affirmatives (`yes` / `ok` / `looks good` / bare `approved`) do **NOT** trigger a write — re-ask with: "Confirming you want me to promote inferred mappings into `course-config.json`. Reply with `alignment approved` to proceed." After explicit approval, Claude writes the inferences into the canonical `course-config.json` fields (`modules[].objectives[].clo` for MLO ladders, `modules[].assessments[].mlo_ids` for assessment mappings), clears the promoted entries from `inferred_mappings.json`, and emits a `alignment_mapping_approved` event to `courses/{id}/timeline.jsonl`.

**Minimum `inferred_mappings.json` schema:**

```json
{
  "schema_version": "alignment-inferred-mappings/v1",
  "course_id": "223754",
  "generated_at": "2026-05-23T18:00:00Z",
  "session_notes": "Optional human-readable summary of the chat-derived mapping decisions",
  "mlo_to_clo": [
    {
      "mlo_id": "MLO1.2",
      "mlo_label": "MLO1.2",
      "proposed_clo_ids": ["CLO1", "CLO4"],
      "rationale": "MLO1.2 spans legal-system structure (CLO1) and case-rule extraction (CLO4) per the module overview's verbatim text."
    }
  ],
  "assessment_to_mlo": [
    {
      "assessment_id": "quiz:2019843",
      "assessment_label": "Module 1 Quiz",
      "module_number": 1,
      "proposed_mlo_ids": ["MLO1.1", "MLO1.2", "MLO1.3"],
      "rationale": "Questions Q1-Q5 cover legal-system structure (MLO1.1), authority types (MLO1.2), and rule synthesis (MLO1.3)."
    }
  ]
}
```

**Approval verb set (exact match required):** `alignment approved` / `faculty approved` / `align approved`. Distinct from push verbs (`push` / `approved` / `deploy`) — alignment writes go to `course-config.json`, NOT Canvas. Never accept bare `approved` (collides with push gate).

**Drift mitigation.** Every report-generation run loads the sidecar fresh. If the ID iterates in chat without writing the sidecar between turns, the report shows the last persisted state — not the in-flight chat state. SKILL.md rule: always write the sidecar BEFORE running report generation if the chat has produced new inferences since the last write.

### Workflow

> **Report generation policy (read first).** The rendered HTML preview is ALWAYS optional and never auto-generates as a side effect of an alignment-review conversation. The orchestrator `alignment_mode_b.py` runs ONLY when the ID's prompt contains an explicit report ask. Examples that DO trigger the orchestrator: `generate report`, `give me the report`, `render the alignment graph`, `make the preview`, `show me the rendered alignment graph`, `produce the report`. Examples that do NOT (Claude stays in chat as thought-partner): `review alignment`, `is this aligned?`, `check CLO/MLO mapping`, `walk me through alignment`, `audit the alignment`. When the trigger phrase is ambiguous (e.g. bare "show me the alignment graph"), Claude must (1) give the chat-side verdict first per the thought-partner workflow, then (2) offer "Want me to render the HTML preview report?" and wait for an explicit yes before invoking the orchestrator. Bare "yes" / "ok" suffice here — this is opt-in to a local-file render, not a Canvas write.

**One-command path (after explicit ID ask only):** when the ID has explicitly asked for the report, Claude runs ONE command:

```
python3 scripts/alignment_mode_b.py --course-id {id}
```

Add `--with-decisions` when the prompt mentions mockups / decisions. Add `--no-open` on non-macOS or non-interactive runs. The orchestrator chains the 3 underlying steps internally and prints a JSON summary that Claude pastes into chat verbatim, followed by a one-line interpretation. Do not paste graph / decisions JSON content into chat.

**Canvas is the source of truth.** `course-config.json` is treated as an OPTIONAL planning-map override (config wins on label collision; text divergence emits `AMBIGUOUS needs_review` edges). The orchestrator runs against any course in the registry without requiring a hand-built config.

**Auto-open.** On macOS, the orchestrator opens the rendered `alignment-preview.html` in the default browser via `open` once the chain completes. `--no-open` suppresses this for headless / scripted runs.

**Underlying steps (still available standalone for finer control):**

1. **Build graph.** Local-config-only path: `python3 scripts/alignment_graph_preview.py --course-id {id}`. Opt-in Canvas-evidence path: `python3 scripts/alignment_graph_preview.py --course-id {id} --include-canvas`. The `--include-canvas` flag is **read-only and opt-in** — it pulls Pages / Assignments / Quizzes / Discussions / Files / ExternalUrls / ExternalTools / quiz questions / assignment rubrics via existing `canvas_api` read helpers, and a regression test asserts that the `create_*` / `update_*` / `add_quiz_question` write helpers raise `AssertionError` if called during a build. Conflicts between local config and live Canvas data are surfaced as `AMBIGUOUS` `needs_review` edges, never overwritten.
2. **Render HTML preview.** `python3 scripts/alignment_preview_html.py --course-id {id}`. Produces a self-contained static HTML page with Course Summary, Filters (module / CLO / MLO / type / confidence), Alignment Paths, Possible Gaps, Assessment Detail, and Faculty Review Notes. Faculty-safe labels: `EXTRACTED` → "Explicit evidence", `INFERRED` → "Likely alignment", `AMBIGUOUS` → "Needs review". Raw confidence values stay in `data-confidence` attrs for filter JS. When the graph has zero CLO or MLO nodes, the renderer emits a top-of-preview "Needs setup review" callout.
3. **Generate mockup decisions** (optional, when the user is working on quiz / assignment / discussion mockups). Run `python3 scripts/alignment_mockup_decisions.py --course-id {id}` to emit `mockup_decisions.json` with per-MLO and per-item KEEP / ADD / REPLACE / OPTIONAL labels plus faculty-safe rationale text. Decision logic — per MLO: `ADD` when no assessment evidence, `KEEP` when any `EXTRACTED` edge, `OPTIONAL` when only `INFERRED`, `REPLACE` when only `AMBIGUOUS`. Per item: confidence → label mapping (`EXTRACTED` → KEEP, `INFERRED` → OPTIONAL, `AMBIGUOUS` → REPLACE). Orphan items default to REPLACE with custom rationale. Filter by assessment node type with `--include-types quiz_question,quiz,rubric_criterion,assignment,discussion`.

### When Mode B feeds `course-mockup`

Generated `mockup_decisions.json` is the authoritative source for per-item KEEP / ADD / REPLACE / OPTIONAL labels in faculty-facing quiz / assignment / discussion mockups. The `course-mockup` skill's Alignment Evidence Integration section documents the read-before-generate pattern.

### Graph schema reference

The `graph.json` artifact uses a typed node + edge model. Faculty-facing surfaces should treat these as the canonical vocabulary when describing alignment evidence:

- **Node types** — `clo`, `mlo`, `page`, `video`, `reading`, `assignment`, `discussion`, `quiz`, `quiz_question`, `rubric_criterion`. Each node carries `id`, `type`, `label`, `text`, `source` (`course-config` or `canvas`), `source_ref`, optional `canvas_url`, and a `metadata` dict (module number, points, parent assessment id, etc.).
- **Edge types** — `aligns_to` (MLO → CLO, item → MLO with explicit alignment), `supports` (material → MLO), `assesses` (assessment or assessment part → MLO), `contains` (assessment → quiz_question / rubric_criterion), `uses` (item → external dataset/tool), `needs_review` (relationship exists but requires faculty confirmation). Each edge carries `confidence` and a verbatim `evidence` string.

If a future schema bump renames or removes a node/edge type, the runtime validator gate in `alignment_graph_preview.py` will reject older artifacts and the SKILL.md contract test will flag the drift.

## Mandatory Handoffs

- If the report finds 0 CLOs in the regex syllabus pass and no declared CLOs in `course-config.json`, the next step is `/course-build` (Course Plan Import Path) to scaffold a CLO baseline.
- If the report writes `remediation_brief.json`, `bulk-edit` can consume its `criteria_to_remediate[]` list in Remediation Brief Mode.
- If the report finds 0 MLOs across all modules, route to `course-build` or manually author module Learning Objectives before reassessing alignment.

## Evidence Anchoring

Every extracted CLO and MLO must include:

- `text`: verbatim source quote
- `page_url`: Canvas URL where the quote appears
- `confidence`: `high` or `low`

Do not paraphrase extracted objectives. If wording must be interpreted, keep the verbatim quote and mark `confidence: low`.

## Limitations

- Mode A does not infer hidden instructor intent; it reports from Canvas-visible syllabus and module overview content.
- Mode B local-only build requires `courses/{id}/course-config.json`. When the config is missing, prompt the user before running `--include-canvas` against an empty local design baseline.
- Relationship wiring is strongest when declared CLO/MLO mappings exist in `course-config.json`.
- Both modes are read-only; they do not modify Canvas content or write staged config changes.
- Missing or unpublished syllabus/module overview content can produce an extraction-failed report (Mode A) or a sparse graph (Mode B) instead of a full alignment matrix.
