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
22 changes: 16 additions & 6 deletions assets/schemas/course.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,12 @@
},
"lesson": {
"type": "object",
"required": ["id", "order", "slug", "title", "context", "concept", "build", "ship", "reflect"],
"description": "A lesson supports TWO authoring shapes (relaxed under DOJ-4007 to unblock retrofit of legacy free-form content for staging seed):\n\n 1. **IDT 5-bucket (recommended)** — `context` + `concept` + `build` + `ship` + `reflect`. Enforces Builder's Bloom pedagogy + Ship-First Design. Use for new lessons authored via the instructional-design-toolkit.\n 2. **Free-form `body`** — single markdown string. Use for legacy / open-ended content that doesn't fit the 5-bucket frame, or for lessons that ship before IDT-style structuring (DemoLab/Articulate Rise parity).\n\n `anyOf` requires AT LEAST one of the two shapes (cannot ship an empty lesson). A lesson MAY provide both: the IDT tooling will prefer the structured fields and treat `body` as supplemental commentary. Course validators downstream (dojo-os ingestion) MUST accept both shapes without branching on which one is present — render the structured fields when available, fall back to `body` otherwise.",
"required": ["id", "order", "slug", "title"],
"anyOf": [
{ "required": ["body"] },
{ "required": ["context", "concept", "build", "ship"] }
],
"additionalProperties": false,
"properties": {
"id": { "type": "string", "pattern": "^lesson:[a-z0-9-]+$", "description": "IMMUTABLE" },
Expand All @@ -197,31 +202,36 @@
"slug": { "type": "string" },
"title": { "type": "string" },
"estimated_minutes": { "type": "integer", "minimum": 5 },
"body": {
"type": "string",
"minLength": 50,
"description": "Free-form markdown body — alternative to the 5-bucket structured fields. Use for legacy / open-ended content or content imported from external authoring tools (Articulate Rise, Notion exports, etc.). IDT tooling emits a WARNING (not an error) when a lesson uses `body` instead of the structured fields, recommending migration to the 5-bucket frame when the content allows."
},
"context": {
"type": "string",
"minLength": 100,
"description": "100-200 word hook that makes the student FEEL the urgency"
"description": "(Recommended) 100-200 word hook that makes the student FEEL the urgency. Required when not using the free-form `body` field."
},
"concept": {
"type": "string",
"minLength": 200,
"description": "300-500 word mental model. Use \\n---\\n to split across Marp slides."
"description": "(Recommended) 300-500 word mental model. Use \\n---\\n to split across Marp slides. Required when not using the free-form `body` field."
},
"build": {
"type": "string",
"minLength": 200,
"description": "50-60% of lesson content. Apply the concept with Claude as partner (NOT teacher)."
"description": "(Recommended) 50-60% of lesson content. Apply the concept with Claude as partner (NOT teacher). Required when not using the free-form `body` field."
},
"ship": {
"type": "string",
"minLength": 30,
"description": "50-100 word deliverable — what the student saves/creates/deploys"
"description": "(Recommended) 50-100 word deliverable — what the student saves/creates/deploys. Required when not using the free-form `body` field."
},
"reflect": {
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"description": "1-2 provocative questions specific to THIS lesson"
"description": "(Recommended) 1-2 provocative questions specific to THIS lesson. Optional but encouraged for both authoring shapes."
},
"marp_slide_count": { "type": "integer", "minimum": 3 }
}
Expand Down
170 changes: 170 additions & 0 deletions assets/schemas/path.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://dojocodinglabs.github.io/instructional-design-toolkit/path.schema.json",
"title": "Path",
"description": "A Dojo Path authoring contract produced by instructional-design-toolkit. A PATH is a SUPER-STRUCTURE *above* the cmi5-course layer (course.schema.json): it references existing courses by id or slug, groups them into ordered MILESTONES, and wraps them in a credential. The per-course cmi5/xAPI contract is UNTOUCHED — a path adds NO AU and NO block; it is purely additive. xAPI identity (additive, no collision with course IRIs): the path activity IRI is `path:<slug>`, each milestone activity IRI is `path:<slug>/milestone:<ordinal>`. The credential is an OpenBadge 3.0 achievement (credential.cert_name / skills_demonstrated / earning_criteria seed the OpenBadge template — NOT a new cmi5 AU). IDs (meta.id, milestones[].id) are IMMUTABLE once created — changing them breaks xAPI history and OpenBadge issuance for learners. Slugs and titles are mutable. Field names align with the dojo-academy content source-of-truth at schemas/path.schema.yml (content/paths/<slug>/path-overview.md frontmatter); this JSON schema is the toolkit authoring contract and additionally carries the meta.id / version cmi5-identity fields the YAML omits.",
"type": "object",
"required": ["meta", "milestones"],
"additionalProperties": false,
"properties": {
"meta": {
"type": "object",
"required": ["id", "version", "slug", "title", "language", "status"],
"additionalProperties": false,
"properties": {
"id": {
"type": "string",
"pattern": "^path:[a-z0-9-]+$",
"description": "IMMUTABLE path identifier. Doubles as the path-level xAPI activity IRI (path:<slug>). Changing it breaks xAPI history and OpenBadge issuance."
},
"version": {
"type": "string",
"pattern": "^\\d+\\.\\d+\\.\\d+$",
"description": "Semantic version. MAJOR=breaking (milestone removed/reordered, course removed from a milestone, credential criteria hardened), MINOR=additive (course or milestone added), PATCH=cosmetic (title/description/presentation tweaks)."
},
"slug": {
"type": "string",
"pattern": "^[a-z0-9-]+$",
"description": "URL + xAPI IRI key. Must be stable once published. Mirrors dojo-academy path.slug."
},
"title": {
"type": "string",
"minLength": 5,
"description": "Mutable display title."
},
"language": {
"type": "string",
"enum": ["es", "en"]
},
"description": {
"type": "string",
"description": "Mutable path summary."
},
"status": {
"type": "string",
"enum": ["draft", "published", "archived"],
"description": "Lifecycle status. Mirrors dojo-academy path.status (default draft)."
},
"level": {
"type": "string",
"enum": ["beginner", "intermediate", "advanced"],
"description": "Aggregate difficulty of the path (independent of Dojo Score rank)."
},
"emblem": {
"type": "string",
"description": "Emoji/glyph for the credential badge when no image URL is set."
},
"faculty": {
"type": "string",
"description": "Owning faculty (e.g. 'Dojo Academy')."
},
"created": { "type": "string", "format": "date" },
"updated": { "type": "string", "format": "date" },
"version_timeline": {
"type": "array",
"description": "Append-only log of versions.",
"items": {
"type": "object",
"required": ["version", "date", "type", "summary"],
"additionalProperties": false,
"properties": {
"version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$" },
"date": { "type": "string", "format": "date" },
"type": { "type": "string", "enum": ["major", "minor", "patch"] },
"summary": { "type": "string" }
}
}
}
}
},
"credential": {
"type": "object",
"description": "OpenBadge 3.0 achievement metadata. This is NOT a new cmi5 AU — it is the path-level credential definition that seeds the OpenBadge template (and dojo-academy paths.{cert_name,skills_demonstrated,earning_criteria} columns).",
"additionalProperties": false,
"properties": {
"cert_name": {
"type": "string",
"description": "Path certificate / credential name (e.g. 'Agentic AI Engineer')."
},
"skills_demonstrated": {
"type": "array",
"items": { "type": "string" },
"description": "Curated path-level skills (distinct from per-course tags). Maps to OpenBadge 3.0 achievement skills."
},
"earning_criteria": {
"type": "array",
"items": { "type": "string" },
"description": "How a learner earns the credential (bullet list). Maps to OpenBadge 3.0 achievement criteria."
}
}
},
"presentation": {
"type": "object",
"description": "Path-card and credential presentation metadata (no pedagogical weight).",
"additionalProperties": false,
"properties": {
"tech_pills": {
"type": "array",
"items": { "type": "string" },
"description": "3-4 most-recognisable technologies the path teaches (path card)."
},
"reward_points": {
"type": "integer",
"minimum": 0,
"description": "Dojo Score reward on completion (reconciled with score-engine)."
},
"credential_badge_url": {
"type": "string",
"format": "uri",
"description": "Optional optimized badge image; overrides emblem when set."
},
"banner_image_url": {
"type": "string",
"format": "uri"
}
}
},
"milestones": {
"type": "array",
"minItems": 1,
"description": "Ordered named phases. Each groups ordered course references. A milestone adds NO cmi5 AU — its xAPI identity is path:<slug>/milestone:<ordinal>.",
"items": { "$ref": "#/$defs/milestone" }
}
},
"$defs": {
"milestone": {
"type": "object",
"required": ["id", "ordinal", "title", "courses"],
"additionalProperties": false,
"properties": {
"id": {
"type": "string",
"pattern": "^(milestone:[a-z0-9-]+|path:[a-z0-9-]+/milestone:\\d+)$",
"description": "IMMUTABLE milestone identifier. Either a bare `milestone:<slug>` or the fully-qualified `path:<slug>/milestone:<n>` form (the latter is the xAPI activity IRI). Changing it breaks xAPI history."
},
"ordinal": {
"type": "integer",
"minimum": 1,
"description": "1-based position of this milestone within the path (teaching order)."
},
"title": {
"type": "string",
"description": "Mutable milestone title."
},
"description": {
"type": "string",
"description": "Mutable milestone summary."
},
"courses": {
"type": "array",
"minItems": 1,
"description": "Member courses in teaching order. Each entry is either a cmi5 course id (`course:<slug>`) or a bare course slug. The path REFERENCES these courses — it never modifies their course.json.",
"items": {
"type": "string",
"pattern": "^(course:[a-z0-9-]+|[a-z0-9-]+)$"
}
}
}
}
}
}
68 changes: 68 additions & 0 deletions commands/new-path.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
description: "Diseño guiado de Path — super-estructura sobre la capa cmi5: agrupa cursos existentes en milestones ordenados y los envuelve en una credential OpenBadge 3.0. No modifica ningún course.json."
argument-hint: "[optional: path-slug si querés revisar/continuar uno existente]"
---

# /new-path — Diseño Guiado de Path

Dispatcher que invoca el skill `instructional-design-toolkit:new-path`.

Un **Path** es una **super-estructura ABOVE the cmi5-course layer**: referencia
cursos existentes por id/slug, los agrupa en **milestones** ordenados, y carga
metadata de credential + presentation. El contrato cmi5/xAPI por-curso queda
**INTACTO** — un path NO agrega ningún AU ni block; es puramente aditivo.

Si `$ARGUMENTS` matchea un path existente en
`content/paths/{slug}/path-overview.md`, el skill detecta el modo revise (sugiere
bumpear versión + changelog). Si vacío o slug nuevo, arranca diálogo guiado desde
cero.

## Qué hace (y qué NO hace)

- **Hace**: pickear cursos miembros (por slug), agruparlos en milestones nombrados
y ordenados, autorear la credential (cert name, skills demonstrated, earning
criteria = metadata OpenBadge 3.0), setear level / emblem / reward.
- **NO hace**: NO modifica ningún `course.json`. Los cursos se referencian por
id/slug; sus AUs cmi5, masteryScore, moveOn y xAPI IRIs quedan tal cual. El path
vive en una capa de identidad propia (`path:<slug>` + `path:<slug>/milestone:<n>`)
que no colisiona con los IRIs de curso.

## Output esperado

```
content/paths/{slug}/
├── path.json ← fuente de verdad (validado contra path.schema.json)
└── path-overview.md ← frontmatter alineado con el contrato dojo-academy
(schemas/path.schema.yml)
```

`path.json` lleva los campos de identidad cmi5 (`meta.id` = `path:<slug>`,
`meta.version` semver) que el YAML de dojo-academy omite. El frontmatter de
`path-overview.md` usa exactamente el shape del contrato dojo-academy
(`slug` / `title` / `description` / `status` / `level` / `emblem` / `faculty` /
`cert_name` / `skills_demonstrated` / `earning_criteria` / `tech_pills` /
`reward_points` / `milestones[{title, description, courses[]}]`) para que
`bun run path:import` lo consuma sin branching.

## Próximos comandos sugeridos

- `/new-track {category}` — mapear el catálogo de cursos + prerequisite graph
- `/course-audit {slug}` — validar un curso miembro contra el framework
- `/new-course {slug}` — crear un curso miembro que todavía no existe

## Overlay invocation (post-base-draft)

Después de producir el base draft (Layer 1 cmi5/xAPI-shaped + Layer 2) de este
comando, seguir `${CLAUDE_PLUGIN_ROOT}/assets/runtime/overlay-protocol.md` para
descubrir y aplicar consumer overlays. El runtime camina
`<cwd>/.claude-plugin/plugin.json`, encuentra skills que declaran
`overlay_target: ["new-path"]` en su frontmatter, los ordena por
`overlay_priority`, y los aplica en orden.

Layer 1 invariants (`meta.id` = `path:<slug>`, `milestones[].id`, semver, la
identidad OpenBadge 3.0 de la credential) permanecen inmutables — outputs de
overlay que los muten abortan el run con un error que apunta al `SKILL.md`
ofensor. Layer 2 contradictions (milestones sin cursos, ordinal gaps, credential
sin earning criteria) loguean un warning visible pero no abortan. Discovery
devuelve cero overlays en un consumer sin `.claude-plugin/plugin.json` — el base
draft del path se escribe directo, voice-neutral, sin warning.
Loading
Loading