Skip to content

Latest commit

 

History

History
293 lines (213 loc) · 12.7 KB

File metadata and controls

293 lines (213 loc) · 12.7 KB

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

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:

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
# 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:

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

Video block:

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

Link block:

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

Callout block:

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:

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:

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.tssignUpTestUser() 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 registrationgraspful register opens browser auth, then stores an API key locally for later CLI/MCP use
  5. API provisioningPOST /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