Skip to content

Latest commit

 

History

History
259 lines (228 loc) · 20.3 KB

File metadata and controls

259 lines (228 loc) · 20.3 KB

gitquiz — Agent Instructions

Interactive quiz platform for reviewing books, podcasts, and courses. Live at quiz.hasit.in.

Skills

Load the relevant skill before starting a domain-specific task:

Task Skill to Load
Converting syllabus/transcript/notes into quiz JSON syllabus-to-quiz
Starting a multi-step task that may trigger compaction hasits-plan

Skills are loaded via OpenCode: <use_opencode_tool><name>skill</name><parameter>name</parameter>syllabus-to-quiz</use_opencode_tool>.

Repository Structure

├── courses/                          # Quiz content (JSON)
│   ├── courses_list.txt              # Catalog of all course folders (IDs only, alphabetically sorted)
│   ├── courses-meta.json             # Course metadata (title, type, chapters, source, description)
│   ├── course-schema.json            # JSON Schema for chapter validation
│   ├── book-<title>/                 # One folder per book/course
│   │   ├── 001.json                  # Chapter 1 questions (array of 7-field objects)
│   │   ├── 002.json                  # Chapter 2 questions
│   │   └── ...
│   ├── podcast-<title>/...
│   └── coursera-<title>/...
├── quiz/                             # Frontend application
│   ├── index.html                    # Single-page quiz app (vanilla JS + Tailwind CDN)
│   ├── styles-theme.css              # Theme variables and dark/light overrides
│   ├── styles-components.css         # UI components (headers, filters, options, etc.)
│   ├── styles.css                    # Animations and keyframes only
│   ├── styles-responsive.css         # Media queries
│   ├── lib/                          # Modular JS (loaded via <script> tags, no bundler)
│   │   ├── state.js                  # Global state, BASE_URL, CATALOG_URL, streak utils
│   │   ├── catalog.js                # Course catalog: load, filter, render, type filters
│   │   ├── preview.js                # Preview screen: fetch module, show first question
│   │   ├── quiz.js                   # Quiz engine: timer, scoring, render, skip, completion
│   │   ├── sharing.js                # Share modal, download PNG (html2canvas), Web Share API
│   │   ├── notifications.js          # Toast notifications (showNotify, showNotifyWithAction)
│   │   ├── ai.js                     # AI Explain: calls Mistral via Cloudflare Worker proxy
│   │   └── main.js                   # Entry point: loads catalog, handles URL params, keyboard shortcuts
│   ├── tests/                        # Playwright end-to-end + schema tests
│   │   ├── *.spec.mjs                # Domain-split spec files
│   │   ├── test-utils.mjs            # Shared mock data & route setup
│   │   ├── affected-tests.mjs        # Git-diff-based test selector
│   │   ├── schema.config.mjs         # Lightweight config for schema tests
│   │   ├── playwright.config.mjs     # Main Playwright config
│   │   └── package.json              # Test dependencies
│   └── proxy/                        # Cloudflare Worker for AI explanations
│       └── worker.js                 # Mistral AI proxy
├── quiz/scripts/                     # Node.js utility scripts
│   ├── answer-length-audit.js        # Detect answer-length bias by word count
│   ├── assemble-course.mjs           # Assembly helper: ch-*.json → input.json
│   ├── coverage-check.js             # Verify concept coverage
│   ├── cross-chapter-repetition.js   # Detect repeated concepts
│   ├── difficulty-audit.js           # Print questions for difficulty labeling
│   ├── difficulty-tally.js           # Tally difficulty distribution
│   ├── fix-length-bias.mjs           # Auto-fix longest-answer bias by truncating conjunctions
│   ├── generate-course.mjs           # CLI generator: input.json → split chapter files + metadata
│   ├── validate-all.js               # Validate all courses comprehensively
│   └── validate.js                   # Validate a single course
├── .opencode/                        # OpenCode AI agent configuration
├── .github/workflows/validate.yml    # CI: schema, validate-all, full Playwright suite
├── opencode.json                     # OpenCode AI config
├── AGENTS.md                         # This file
└── README.md                         # Full reference documentation

Quiz JSON Format

Each course folder contains numbered chapter files (001.json, 002.json, ...). Each file is a JSON array of question objects with 7 required fields + 1 optional field:

Field Type Required Description
question string Yes Short concept name (e.g., "Opportunity Cost")
content string Yes Brief 1-2 sentence explanation of the concept
description string Yes Real-world scenario ending with a question
options string[] Yes Array of exactly 4 plausible answer strings. No positional refs.
answer string Yes Correct answer — must be identical (case, punctuation, whitespace) to one option
explanation string Yes Teaching explanation
difficulty string Yes "easy", "medium", or "hard"
blank string No Cloze passage containing answer verbatim, for fill-in-the-blank mode
[
  {
    "question": "Concept Name",
    "content": "Brief explanation (1-2 sentences).",
    "description": "Real-world scenario. What does this demonstrate?",
    "options": [
      "Incorrect option 1",
      "Incorrect option 2",
      "Correct option",
      "Incorrect option 4"
    ],
    "answer": "Correct option",
    "explanation": "Why this is correct and the others are not.",
    "difficulty": "easy"
  }
]

Rules

Content & Quiz Rules

  1. 7 required fields per question: question, content, description, options (array of 4), answer, explanation, difficulty. No extra fields, no missing fields. An optional blank field may be added for cloze/fill-in-the-blank support.
  2. Answer must match option exactly: The answer string must be character-for-character identical to one entry in options. Trailing spaces, capitalization, and punctuation differences all cause validation failures.
  3. 4 options only: Exactly 4 strings in options. No fewer, no more.
  4. No positional references: Options must not reference other options by letter/position (e.g., "Both A and B", "All of the above", "A & C", "None of the above"). These break when the JS runtime shuffles option order.
  5. Difficulty values: Only "easy", "medium", or "hard" (lowercase).
  6. Per-chapter difficulty targets:
    • Easy: 30-40% of questions
    • Medium: 35-45%
    • Hard: 15-25%
  7. 7-12 questions per chapter file: Each 00N.json file should have 7-12 question objects. More than 12 is allowed but not recommended.
  8. Chapter files numbered 001.json, 002.json, etc.: Zero-padded 3-digit numbers in filenames.
  9. courses_list.txt alphabetically sorted: After adding a new course, insert its ID in alphabetical order among existing entries.
  10. courses-meta.json keys match courses_list.txt: Every key in courses-meta.json must appear in courses_list.txt and vice versa. Both must be sorted identically. The chapters field must match Get-ChildItem courses/<id>/*.json | Measure-Object | Select-Object -ExpandProperty Count.
  11. Answer word-count balance: All 4 options must be within ±20% of the mean word count of those 4 options. The answer must not be uniquely longest/shortest (a known length-hint signal). Mechanical templated padding (e.g., appending "which is a critical factor to consider in this context") is banned — this suffix appears on 1,431 options in the existing corpus and is a detectable cheat signal. Use node quiz/scripts/answer-length-audit.js to detect both ratio violations and banned padding. Fix bias by writing context-appropriate expansions for each option, never a copy-paste template.

Workflow Rules

  1. Skills first: Load hasits-plan for any multi-step task (3+ steps or any task that may trigger compaction). Load syllabus-to-quiz for all course content work. Never create/modify quiz JSON outside the skill.
  2. Generator for 5+ chapters: Use node quiz/scripts/generate-course.mjs input.json to create chapters from a structured input file (avoids PowerShell quoting issues). Supports --dry-run for preview. The generator auto-updates courses_list.txt and courses-meta.json. Delete input.json after use. For courses with many chapters, use the intermediate ch-*.json workflow: write ch-001.jsonch-00N.json files in the course directory, then node quiz/scripts/assemble-course.mjs <course-id> to produce input.json for the generator.
  3. Validate before committing: Run both node quiz/scripts/validate-all.js and npm run test:schema before committing course content changes. A pre-commit hook (.githooks/pre-commit) auto-runs both when course files are staged; enable it with git config core.hooksPath .githooks after cloning.
  4. Don't validate courses for code-only changes: If no files under courses/ were touched (git diff --name-only has no courses/ entries), skip validate-all.js and test:schema. Only run Playwright tests relevant to the changed lib files. Batching schema validation on every code change wastes time.
  5. PowerShell execution policy: Prefix failing commands with powershell -ExecutionPolicy Bypass -Command "...".
  6. All paths relative to project root unless stated otherwise.
  7. Single-file edits: Validate each file before moving to the next. Don't batch edits across chapters.
  8. Run affected tests before commit: Use node quiz/tests/affected-tests.mjs to see which tests are relevant to your changes, then run the suggested subset.
    • npm run test:setupquiz/lib/main.js, quiz/lib/state.js, quiz/lib/notifications.js
    • npm run test:catalogquiz/lib/catalog.js
    • npm run test:previewquiz/lib/preview.js
    • npm run test:quizquiz/lib/quiz.js, quiz/lib/state.js
    • npm run test:paramsquiz/lib/main.js
    • npm run test:uiquiz/lib/sharing.js
    • npm run test:aiquiz/lib/ai.js
    • npm run test:unitquiz/lib/state.js, quiz/lib/catalog.js, quiz/lib/sharing.js (isolated function tests)
    • npm run test:visual — visual-only changes
    • npm run test:schemacourses/**/*.json, courses/course-schema.json

Frontend Conventions

  1. Catalog test conventions:
    • CATALOG_CONTENT = \n-joined course IDs
    • MOCK_META = mock courses-meta.json object with { chapters, title, type, source, description }
    • MOCK_MODULES = { "001.json": [...], "002.json": [...] } for multi-chapter testing
    • Use toHaveAttribute('data-value', …) for .list-item
    • Type filter buttons use data-type (e.g., .type-filter-btn[data-type="book"])
    • #course-dropdown has w-full class
    • New course indicator tests: set quizSeenCourses localStorage, check NEW pill badge visibility (.new-badge), sort order (unseen first)
  2. Course ID display:
    • Type prefix stripped via /^(book|podcast|coursera|course)-/i
    • Emoji prefix (📘/🎙/📖) shown only when activeTypeFilter === 'all'
    • Kebab-case → Title Case for display
    • New courses (not yet seen) append NEW pill badge (.new-badge); sort order: unseen first
  3. Overflow-prone DOM elements (must not push content off-screen):
    • #preview-badge — course ID badge. Has truncate max-w-[200px]
    • #preview-title — course name (h2). Add mobile truncation in styles-responsive.css
    • #module-label — quiz header span between "← Menu" and "Skip Module". Add max-width: 140px + mobile truncation; without it "Skip Module" gets pushed off-screen
    • #course-dropdown .list-item — flex row with type SVG icon, title, and optional NEW pill badge; overflow hidden on text, item gets title attribute via renderCatalogOptions() for full-name hover tooltip
    • #begin-btn-wrapper — normal flow on mobile (not sticky)
    • #preview-chapter-grid — flex-wrap chapter buttons, no truncation needed
    • #topic-title, #description-text, #content-box — wrapping OK, no truncation
  4. Styling:
    • Tailwind CSS via CDN (no build step) — utility classes in HTML
    • Custom CSS (4 files): styles-theme.css (variables), styles-components.css (components), styles.css (animations), styles-responsive.css (media queries)
    • JS toggles classes (hidden, correct, wrong, screen-enter, score-pop)
    • Mobile overrides use @media (max-width: 640px)
  5. Screen transition animation: The screen-enter CSS class triggers fadeSlideIn animation (0.3s ease-out). Applied to preview, quiz-flow, and completion-screen on reveal.
  6. Progress bar transition: #progress-fill has transition: width 0.3s ease for smooth animation.
  7. Focus indicators: :focus-visible outline (2px var(--accent), offset 2px) on #catalog-search, #share-btn, #course-dropdown.

Quiz Engine Conventions

  1. Scoring: score += 100 + max(0, 50 - timeSpent * 5) + (streak > 2 ? 20 : 0)
  2. Streak: Consecutive correct answers. Resets to 0 on wrong answer.
  3. Timer: secondsElapsed increments every second during quiz. Shown in #timer-val.
  4. Daily streak: Stored in localStorage key quizDailyStreak as { lastDate: "YYYY-MM-DD", count: <number> }. Updated on quiz start and completion.
  5. Course metadata: coursesMeta (loaded from META_URL via loadCatalog()) stores per-course { chapters, title, type, source, description }. Used by preview to show chapter count and chapter grid.
  6. New course tracking: Stored in localStorage key quizSeenCourses via markCourseSeen(id), queried by isCourseNew(id). Unseen courses show NEW pill badge (.new-badge) and sort before seen ones in the dropdown.
  7. Options shuffling: Options are shuffled via Fisher-Yates inside shuffleArray() in quiz.js. Answer matching is done against the original (pre-shuffle) text.
  8. Module chaining: After the last question of a chapter, if the course has more chapters, "Start Next Module" button appears. If it was the last chapter, "Return to Catalog" appears with trophy animation.
  9. Custom URL loading: The user can paste any JSON URL — no path validation required. The URL section (#url-section, #quiz-url) is lazy-created by getOrCreateUrlSection() in catalog.js on first toggleUrlInput() call — not in initial HTML. The toggle button sits in the footer as 🔗 Custom Quiz. Code that references #quiz-url must guard with document.getElementById('quiz-url') null check.
  10. AI Explain flow: 4 persona buttons (.ai-persona-btn[data-persona]) trigger askAI(persona). Personas: child (simple), deep (expert), first-principles (fundamental truths), socratic (guiding questions). POSTs to MISTRAL_PROXY_URL with persona-specific system prompt. Shows response in #ai-response. Falls back gracefully on failure.
  11. Sharing context:
    • Completion screen → share certificate + score
    • Quiz active → share question + user's answer
    • Catalog screen → share portal link
  12. Achievement card: Template hidden off-screen at #achievement-card-template (CSS left: -9999px). Uses html2canvas to render to PNG.
  13. XSS hazard: no URL interpolation into inline handlers: quiz.js:272 built <button onclick="initializeQuiz('${nextUrl}')"> via innerHTML — a crafted URL with ' breaks the string boundary and enables arbitrary JS execution. Completion-screen code must use addEventListener / document.createElement, never string interpolation into onclick (or similar) attributes.
  14. Options trailing-whitespace edge case: quiz.js:196 compares correctAnswer (trimmed via .trim()) against dataset.option (untrimmed). If any option in a JSON file has trailing whitespace, the correct-answer highlight after a wrong answer silently fails. Validate that answer and its matching options entry are exactly identical with no leading/trailing whitespace (reinforces rule 2 above).

Test Conventions (quiz/tests/)

Two test runners coexist:

  • Node node:test (.test.mjs files) — pure function unit tests and schema validation. No browser, no Playwright dependency. Run via node --test *.test.mjs or npm run test:unit / npm run test:schema.
  • Playwright (.spec.mjs files) — DOM interaction and visual regression tests. Requires npx playwright test <file>.

Node test conventions (lib-unit.test.mjs, schema-valid.test.mjs):

  1. Pure functions extracted into test-helpers.mjs (imported by unit tests) — duplicates lib/ logic for testability without refactoring app globals
  2. Uses node:test (describe/it) and node:assert/strict
  3. schema-valid.test.mjs validates all JSON files dynamically (no Playwright browser, no AJV in this file — uses plain JSON traversal)

Playwright conventions:

  1. Opens file:///.../quiz/index.html via page.goto()
  2. Uses devices['iPhone X'] for mobile screenshots in visual tests
  3. Common operations: page.waitForFunction(), waitForSelector(), el.scrollIntoView()
  4. Snapshots: expect(await page.screenshot()).toMatchSnapshot(...) with threshold: 0.01
  5. Main config (playwright.config.mjs): Two projects in sequence (Desktop 1280×800, Mobile Pixel 5), webServer on port 8765, fullyParallel: false
  6. Playwright helpers (test-utils.mjs):
    • CATALOG_CONTENT = \n-joined course IDs
    • MOCK_MODULES = { "001": [...questions...], "002": [...questions...] }
    • setupMockRoutes() = intercepts catalog, meta, and module URLs
    • createMockQuestion() = generates a question with given overrides
    • MOCK_META = mock courses-meta.json (used by catalog and preview tests)
    • test-helpers.mjs also used by Playwright tests that import pure functions directly
  7. Affected test mapping (in affected-tests.mjs):
    • courses/schema-valid.test.mjs
    • quiz/lib/main.jssetup.spec.mjs, url-params.spec.mjs
    • quiz/lib/state.jssetup.spec.mjs, quiz.spec.mjs, lib-unit.test.mjs, catalog.spec.mjs, preview.spec.mjs
    • quiz/lib/catalog.jscatalog.spec.mjs, lib-unit.test.mjs
    • quiz/lib/preview.jspreview.spec.mjs
    • quiz/lib/quiz.jsquiz.spec.mjs, navigation.spec.mjs
    • quiz/lib/sharing.jsui.spec.mjs, lib-unit.test.mjs
    • quiz/lib/notifications.jssetup.spec.mjs
    • quiz/lib/ai.jsai.spec.mjs
    • quiz/styles.css, quiz/styles-components.css, quiz/styles-theme.css, quiz/styles-responsive.cssvisual.spec.mjs
    • quiz/index.html → all spec and test files
    • quiz/tests/test-helpers.mjslib-unit.test.mjs
    • quiz/tests/test-utils.mjscatalog.spec.mjs, preview.spec.mjs, setup.spec.mjs, quiz.spec.mjs
    • courses/course-schema.jsonschema-valid.test.mjs

Course Metadata Conventions

  • courses-meta.json keys are course IDs (e.g., "book-atomic-habits")
  • Fields: title (string), type (enum: book/podcast/coursera), chapters (number), source (string|null), description (string)
  • The type field determines the emoji prefix shown in the UI
  • The chapters field must match the actual number of chapter JSON files on disk

CI Details

  • .github/workflows/validate.yml has 3 jobs: schema, validate-all, full-suite
  • full-suite uses dorny/paths-filter to skip when only courses/** or *.md changed
  • Node.js version: 24. Cache: npm for quiz/tests/package-lock.json
  • npm run test:schema runs with node --test schema-valid.test.mjs
  • Scheduled daily at 11:56 UTC

OpenCode Configuration

  • opencode.json specifies DeepSeek V4 Flash as the primary model, Mistral Large and OpenAI o3-mini as fallbacks
  • Nemotron 3 Super DGX used for embeddings
  • Context compaction enabled at 80K tokens
  • AGENTS.md added to compactifyIncludes for context survival
  • Skills loaded on startup: hasits-plan, customize-opencode