# Graspful

Adaptive learning platform. Multi-tenant white-label SaaS.

## Critical Rules

- **CLI or MCP for everything.** Browser auth is only used to mint credentials for the CLI; course creation and publishing stay programmatic.
- **Use `bun` not `npm`.**
- **Read `docs/adding-a-course.md` before building any course.** It is the canonical agent runbook.
- **Auth, routing, and entitlement changes require regression proof.** Verify anonymous access, signed-in but non-entitled access, and entitled access, and cover shell/chrome expectations on the affected route with automated tests. For user-facing access control changes, run the relevant e2e path before considering the work done.

## Course Creation — Agent Workflow

> If a user asks you to build, draft, author, or create a course, follow this section. Do NOT improvise a workflow — use the CLI/MCP tools below.

### Authentication — Read This First

**You can scaffold, fill, validate, and review courses without any account.** These operations run locally.

**You MUST authenticate before importing or publishing.** Four tools require auth: `graspful_import_course`, `graspful_publish_course`, `graspful_import_brand`, `graspful_list_courses`.

To authenticate, do ONE of:
1. **CLI (recommended for first-time setup):** Run `graspful register [--email <email>]` — this opens browser auth, then saves an API key to `~/.graspful/credentials.json`.
2. **Environment variable:** Set `GRASPFUL_API_KEY=gsk_...` before starting the MCP server.

If you try an authenticated tool without auth, you'll get a clear error telling you to register first.

### Step 1: Install the CLI

```bash
npx @graspful/cli init
```

This auto-detects your editor (Claude Code, Cursor, Windsurf, Codex) and configures MCP if supported. No global install needed.

If you have MCP configured, skip the CLI and use MCP tools directly (see Step 1b).

### Step 1b: MCP alternative

If MCP is already configured, you have these tools available — no CLI needed:

| Tool | Auth? | Description |
|------|:---:|-------------|
| `graspful_create_academy` | No | Generate academy manifest YAML |
| `graspful_scaffold_course` | No | Generate course YAML skeleton |
| `graspful_fill_concept` | No | Add KPs and problems to a concept |
| `graspful_validate` | No | Validate YAML against schema |
| `graspful_review_course` | No | Run 10 quality checks, including teaching alignment |
| `graspful_describe_course` | No | Course statistics |
| `graspful_create_brand` | No | Generate brand YAML |
| `graspful_import_academy` | **Yes** | Import academy manifest + course YAMLs (set `GRASPFUL_API_KEY` first) |
| `graspful_import_course` | **Yes** | Import course to platform (set `GRASPFUL_API_KEY` first) |
| `graspful_publish_course` | **Yes** | Publish a draft course (set `GRASPFUL_API_KEY` first) |
| `graspful_import_brand` | **Yes** | Import brand config (set `GRASPFUL_API_KEY` first) |
| `graspful_list_courses` | **Yes** | List org courses (set `GRASPFUL_API_KEY` first) |

Tools marked "No" for auth work offline — no account needed. Tools marked **Yes** will fail with a clear error if you haven't authenticated. Run `graspful register`, then restart MCP with `GRASPFUL_API_KEY`.

**MCP discovery:** To check if MCP is active, try calling `graspful_validate` with any small YAML string. If it responds, MCP is working. If you get a "tool not found" error, fall back to the CLI.

### Step 2: Register (before importing/publishing)

If you haven't already authenticated (see "Authentication" above), do it now before proceeding to import.

**CLI:**
```bash
graspful register --email <email>
```

This creates an account, org, and API key through browser auth. To use MCP tools that require auth, restart the MCP server with `GRASPFUL_API_KEY` set to the saved key.

### Step 3: Build a course

The workflow is: scaffold -> fill -> validate -> review -> import.

**Before writing any YAML**, follow the detailed runbook in `docs/adding-a-course.md`. Key steps:
1. Gather source material (official docs, syllabi, PDFs — not marketing copy)
2. Model it as an academy first, even if it starts with one course
3. Decompose the academy into foundations -> structures -> operations -> applied judgment
4. Build the prerequisite graph (roots -> trunk -> branches -> leaves)
5. Write the YAML skeleton (graph first, content second)
6. Spend a cycle on the academy landing page so the promise and proof are specific to the learner
7. Fill concepts one at a time
8. Validate and review

```bash
# 1. Scaffold the academy shell
graspful create academy --topic "Your Topic" -o academy.yaml

# 2. Scaffold the first course knowledge graph
graspful create course --topic "Your Topic" --hours 10 -o course.yaml

# 3. Fill each concept with knowledge points and problems
graspful fill concept course.yaml <concept-id>

# 4. Validate after every edit
graspful validate course.yaml

# 5. Review — must score 10/10 to publish
graspful review course.yaml

# 6. Import and publish
graspful import academy.yaml --org <org-slug> --course-dir . --publish
```

Or with MCP tools:

```
graspful_create_academy(topic: "Your Topic")
graspful_scaffold_course(topic: "Your Topic", estimatedHours: 10)
-> edit the YAML
graspful_fill_concept(yaml: "...", conceptId: "concept-id")
graspful_validate(yaml: "...")
graspful_review_course(yaml: "...")
graspful_import_academy(manifestYaml: "...", courseYamls: { "courses/course.yaml": "..." }, org: "org-slug", publish: true)
```

### Images, Videos, Links in Course Content

Course YAML supports rich media through **content blocks** on two fields: `instructionContent` and `workedExampleContent`. Each is an array of typed blocks.

**Image block:**
```yaml
instructionContent:
  - type: image
    url: https://example.com/photo.jpg
    alt: Description for accessibility
    caption: Optional caption text
    width: 960  # optional, positive integer
```

**Video block:**
```yaml
instructionContent:
  - type: video
    url: https://youtube.com/watch?v=abc123
    title: Video title
    caption: Optional caption
```

**Link block:**
```yaml
instructionContent:
  - type: link
    url: https://example.com/reference
    title: Link title
    description: Optional description
```

**Callout block:**
```yaml
instructionContent:
  - type: callout
    title: Important distinction
    body: The explanation text here.
```

**Important:** `instruction` and `workedExample` (the plain text fields) should remain readable as standalone text because they power audio. Put images, diagrams, external references, and video links in the `*Content` blocks — do not bury URLs in the prose.

When a user asks for images or visual comparisons in a course, use `image` content blocks with publicly accessible URLs. Every knowledge point can have multiple content blocks.

### Working from source material (PDFs, documents, etc.)

When building a course from a PDF or document:

1. Read the full source material first
2. Extract the key concepts, facts, and distinctions
3. Map them to a prerequisite graph (what must be learned before what?)
4. For visual content (photos, diagrams, comparisons), find or request publicly accessible image URLs and use `image` content blocks
5. Do not copy-paste prose verbatim — rewrite for the lesson pattern (instruction -> worked example -> problems)

### Step 4: Brand and landing page

Treat the landing page as part of the academy build:

- the copy must describe the actual learner, outcome, and curriculum promise
- the page should explain why this academy exists and why a learner should trust it
- generic placeholder niche copy is not sufficient

Create a white-label landing page and theme:

```bash
graspful create brand --niche tech --topic "Your Topic" --name "My Academy" --org my-org -o brand.yaml
graspful import brand.yaml
```

### Key rules

- **CLI or MCP for everything.** Browser auth is only for minting credentials; course creation, import, and publishing stay programmatic.
- **Validate after every edit.** It's offline and fast.
- **Fill one concept at a time.** Quality is better than filling the whole course at once.
- **Review before import.** The server rejects courses that fail the quality gate.
- **Problem IDs must be globally unique.** Use `{concept-id}-{kp-index}-p{problem-index}`.

### Output format

Pass `--format json` to any CLI command for machine-readable output:

```bash
graspful validate course.yaml --format json
```

### Environment variables

| Variable | Description |
|----------|-------------|
| `GRASPFUL_API_KEY` | API key for import/publish (set automatically by `register` or `login`) |
| `GRASPFUL_API_URL` | API base URL (default: `https://api.graspful.ai`) |

## Tech Stack

- **Frontend:** Next.js 16 (App Router), React, Tailwind CSS, shadcn/ui
- **Backend:** NestJS (TypeScript), Prisma ORM, PostgreSQL (Supabase-hosted)
- **Auth:** Supabase Auth (JWT)
- **Monorepo:** Turborepo, bun as package manager

## Architecture — DDD Bounded Contexts

The backend follows Domain-Driven Design with bounded contexts. Each NestJS module owns its domain. **Do not leak domain logic across boundaries.**

| Context | Module | Aggregate Root | Owns |
|---------|--------|---------------|------|
| Knowledge Graph | `knowledge-graph/` | Course | Concepts, KnowledgePoints, PrerequisiteEdges, EncompassingEdges |
| Student Model | `student-model/` | StudentProfile | ConceptState, KPState, mastery, enrollment |
| Diagnostic | `diagnostic/` | DiagnosticSession | BKT engine, MEPE selector, stopping criteria, session persistence |
| Learning Engine | `learning-engine/` | LearningSession | Task selection, frontier, mastery enforcement, remediation |
| Assessment | `assessment/` | Assessment | Problems, answer evaluation, reviews, quizzes |
| Spaced Repetition | `spaced-repetition/` | RepetitionSchedule | FIRe algorithm, review scheduling |
| Gamification | `gamification/` | PlayerProgress | XP, streaks, leaderboards |

### DDD Rules

1. **Services call services, not repositories of other modules.** If Diagnostic needs mastery data, it calls `StudentStateService`, not `prisma.studentConceptState` directly.
2. **Controllers are thin.** Extract, validate, delegate to service, return. No domain logic in controllers.
3. **Each module owns its Prisma queries.** Other modules request data through the owning module's service.
4. **Cross-context data for the frontend** should be composed at the API/controller layer or in a dedicated query service — not by having one domain service reach into another's tables.

## Backend

- Build: `cd backend && /path/to/tsc -p tsconfig.build.json --outDir dist` (nest build has symlink issues with bun)
- Run: `TS_NODE_PROJECT=tsconfig.runtime.json node -r tsconfig-paths/register dist/main.js`
- Dev: `bun run dev` (nest start --watch)
- Test: `bun run test`
- Port: 3000

## Frontend

- Dev: `bun run dev` (port 3001)
- Build: `npx next build`
- E2E: `cd apps/web && npx playwright test`

## Prisma

- Schema: `backend/prisma/schema.prisma`
- Migrate: `cd backend && npx prisma migrate dev --name <name>`
- Generate: `npx prisma generate` (runs automatically after migrate)

## Conventions

- Use `bun` not `npm`
- snake_case for DB columns (Prisma `@@map`), camelCase for TypeScript
- All Prisma models use `@db.Uuid` for IDs and `@db.Timestamptz` for dates
- Tests: Jest for backend unit tests, Playwright for e2e
- E2E helpers: `apps/web/e2e/helpers/auth.ts` — `signUpTestUser()` creates fresh users
- Brand cookie: `dev-brand-override` selects org in dev

## E2E Test Coverage Requirements

**Every live site page and API endpoint MUST have an e2e test.** This is non-negotiable — bread-and-butter functionality that users depend on must have regression coverage.

### What must be tested:

1. **All pages render** — every route under `(marketing)` and `(app)` must have a smoke test verifying it returns 200 and renders its heading. See `e2e/docs-smoke.spec.ts` for the pattern.
2. **Auth flows** — sign-up, sign-in, sign-out, email confirmation callback, org provisioning
3. **Creator flows** — API key CRUD, course import, brand config import, course publish
4. **CLI registration** — `graspful register` opens browser auth, then stores an API key locally for later CLI/MCP use
5. **API provisioning** — `POST /auth/provision` creates personal org for web UI sign-ups
6. **Learner flows** — browse, enroll, diagnostic, study session

### When adding a new page or endpoint:

- Add an e2e test in the same PR
- If it's a new doc page, add it to the `DOCS_PAGES` array in `e2e/docs-smoke.spec.ts`
- If it's a new API endpoint, add API-level tests using the `helpers/api-auth.ts` helpers

## Links

- Documentation: https://graspful.ai/docs
- CLI reference: https://graspful.ai/docs/cli
- MCP setup: https://graspful.ai/docs/mcp
- Course YAML schema: https://graspful.ai/docs/course-schema
- Brand YAML schema: https://graspful.ai/docs/brand-schema
- Full docs for LLMs: https://graspful.ai/llms-full.txt
