A Claude Code project that scans your school's Canvas LMS for pending assignments, plans the work, and dispatches each item to a course-specific skill that produces a draft (or, where you have explicitly authorized it, submits on your behalf).
You stay in the loop on the two decisions that matter — what to do and what to ship. The framework handles the rest.
A typical interaction once you're fully configured:
You (in Claude Code, this directory): scan canvas
Canvas Pilot:
🔥 URGENT (next 72h)
| # | course | assignment | due | proposed skill |
| 1 | Code Course A | Set 3 Problem 1 | 4/25 23:59 | canvas-ics33 |
| 2 | Writing Course| Tue Wk5 HW Scan | 4/28 12:30 | canvas-reading-annotation |
⚠️ SOON (3-7d)
| 3 | Quiz Course | Section 5 | 4/27 23:59 | canvas-inside |
Reply: "approve all" / "approve 1, 2" / "only urgent" / "cancel"
You: approve all
Canvas Pilot:
[dispatches each item sequentially, runs the per-course skill,
produces a draft under runs/<today>/<assignment>/draft/,
and writes a status to result.json]
Done. REPORT.md written at runs/<today>/REPORT.md.
Drafts mirrored to final_drafts/ for review.
You review the drafts. You upload manually — unless you have given a specific per-course skill standing authorization to auto-submit, in which case Canvas Pilot uploads after a verification gate passes.
- Students whose school uses Canvas LMS.
- Whose coursework includes a high share of repeating-structure assignments (weekly problem sets, weekly readings, weekly quizzes).
- Who want an AI to handle the boring orchestration (scan, dispatch, draft) but keep the human firmly in the loop for review and submission.
- Who already use Claude Code as their daily editor.
This is not a "submit my homework for me" tool. The default behavior is produce drafts, you submit. Auto-submit is opt-in per skill, per assignment type, with a verification gate in front.
There is one entry point: you say scan canvas in a Claude Code session in
this directory. Everything else is a branch that canvas-scan decides based on
filesystem state.
You: "scan canvas"
│
└─→ canvas-scan §0 self-check
│
├─ .env missing / CANVAS_BASE empty
│ │
│ └─→ canvas-setup
│ 1. Ask for your school's Canvas URL
│ 2. Silent install of Playwright + Chromium
│ 3. Pop browser, you log in once
│ 4. List your active courses, ask which to track
│ └─→ when done, hands off to canvas-bootstrap (no exit)
│
├─ .env OK but courses.yaml.routes is empty
│ │
│ └─→ canvas-bootstrap
│ • Configures ONE course (cluster) per invocation.
│ Not batch — you walk away after each one and
│ re-trigger when you want the next.
│ • Recommendation order is a hard rule:
│ 1. 🟢 Non-LDB quiz ← agent ships end-to-end with
│ zero human-side config
│ 2. 🟢 Code course ← verification is mechanical
│ (test runner / compile / lint)
│ 3. 🟡 Writing ← rubric is bespoke; needs you
│ to author voice + samples
│ 4. ⚪ Unclear → route to canvas-generic
│ (runtime-designed fallback, no
│ overlay; investigates + designs
│ pipeline per-assignment)
│ 5. 🔴 LDB-locked → route to canvas-skip
│ (Lockdown Browser is
│ intrinsic can't-do)
│ • For categories 1-3 (specific-skill clusters):
│ silent deep-investigate
│ → spawn 3 sub-agents in parallel (rubric coverage /
│ verification checklist / feasibility simulator)
│ → single batched ask to you
│ → write overlay v1 at _private/canvas-<name>-app.md
│ (Category 4 skips this — no overlay needed for canvas-generic)
│ • If a pending real assignment exists for the cluster,
│ run the FIRST-RUN CALIBRATION LOOP:
│ - dispatch the per-course skill, produce one draft
│ - you review the draft and give feedback
│ - Sub-agent D categorizes each piece of feedback:
│ recurring_pattern → write back into overlay v2
│ one_off → fix this draft only
│ workflow_change → write back into overlay v2
│ - you confirm the categorization
│ - first_run_calibration_done = true
│ • Returns to you. Re-trigger scan to configure the
│ next cluster.
│
└─ .env OK + courses.yaml.routes non-empty
│
└─→ Normal scan
• List pending → write plan.json → STOP.
• You reply with approval ("approve all" / "do 1, 3" /
"only urgent" / "cancel").
• canvas-execute reads plan.json + your approval,
dispatches approved items in sequence:
├─ canvas-ics33
├─ canvas-reading-annotation ┐
├─ canvas-essay ┘ src/ac_eng_router.py
├─ canvas-zybooks picks one of the two
├─ canvas-inside
├─ canvas-generic ← runtime-designed fallback
└─ canvas-skip
• Each per-course skill writes its result to
runs/<today>/<assignment>/result.json.
• When all are done: REPORT.md is written, drafts are
mirrored to final_drafts/ for human review.
Two non-obvious things about this flow:
-
canvas-bootstrapis single-cluster on purpose. A typical student has 3-4 trackable courses. Configuring all four in one session means a wall of text you can't reason about. Configuring one, walking away, coming back tomorrow for the next gives you a tangible deliverable per session and lets you reuse what you learned from the first cluster when you do the next. -
The first time you run a per-course skill, it is a co-authoring session with you, not a one-shot.
canvas-bootstrap's overlay v1 is the skill's best guess from reading the cluster. v2 is what you actually want, and it only exists after you've reviewed a real draft and your feedback has been categorized. Plan for ~30 minutes per cluster for the first-run loop. Subsequent runs of the same skill are non-interactive.
- Clone this repo somewhere local.
- Open the folder in Claude Code.
- Say
scan canvas(or anything similar — "check my canvas", "do my homework",/canvas-scan).
Claude Code dispatches the canvas-scan skill. It sees no .env and
auto-dispatches canvas-setup:
- Asks for your Canvas instance URL (e.g.
canvas.<your-school>.edu). - Installs Playwright + Chromium silently (~150 MB, one-time).
- Opens a browser. You log in to Canvas with your school SSO once. The skill captures the session cookie and stores it locally.
- Lists your active courses. You pick which ones to track.
canvas-setup then hands off to canvas-bootstrap, which starts
configuring your first course. You pick a cluster from the
recommendation list. The skill investigates it silently, asks you ~5
questions in one batch, writes the overlay, and either (a) runs a
calibration loop on a pending real assignment if one exists, or (b)
defers calibration to your next scan.
You say scan canvas. If courses.yaml.routes has at least one entry
but not all your courses, you get the normal scan PLUS a one-line
reminder that other courses are still unconfigured. You re-trigger
canvas-bootstrap whenever you want to configure another.
Once all your courses are configured, scan canvas is the only command
you ever need:
scan canvas
→ review plan
→ "approve all" / "approve 1, 2" / "only urgent" / "cancel"
→ wait for drafts
→ read REPORT.md, eyeball final_drafts/, upload manually (or rely on
standing auto-submit authorization for skills you've granted it to)
You answer ~3 questions per cluster and log into Canvas once per session-cookie-expiry. Everything else happens silently through Claude Code's tooling.
Prefer to set up by hand? See SETUP.md.
Canvas Pilot ships ten skills under .claude/skills/. Five are
framework-level (you use them as-is on any course). Four are per-course
skeletons that load a local overlay file describing your specific school
and instructor. One additional skill (canvas-essay) is part of the
writing-course handler. One catch-all (canvas-generic) handles
assignments that don't fit any of the specific skills — it discovers the
spec, designs a pipeline, and writes a draft at runtime, without an
overlay.
| Skill | What it does |
|---|---|
canvas-setup |
First-run install: ask for Canvas URL, silent dependency install, pop browser for one login, list courses, save selection. Auto-dispatched by canvas-scan when .env is missing. |
canvas-bootstrap |
Surveys a new course's recurring patterns and drafts a per-course overlay. Runs one cluster per invocation, scope-first. Includes the first-run calibration loop that turns the student's feedback on a real draft into overlay v2. |
canvas-scan |
Reads courses.yaml, queries Canvas for assignments in the pending window, writes plan.json, stops. Also acts as the entry-point router that dispatches canvas-setup or canvas-bootstrap when the repo is not yet fully configured. |
canvas-execute |
Reads the user's approval reply against plan.json, dispatches approved items to their per-course skill, gathers results, writes REPORT.md, mirrors drafts to final_drafts/. |
canvas-skip |
Punts an assignment to manual handling. Logs to the daily todo.md, returns skipped status. Used for Lockdown Browser quizzes and any other intrinsic can't-do. |
Each per-course skeleton is a generic framework for a class of course.
The school- and instructor-specific behavior lives in a local overlay
file at _private/canvas-<name>-app.md, which is gitignored and never
leaves your machine. If the overlay does not exist, the skeleton stops
and tells you to author one (or invoke canvas-bootstrap to draft it
interactively).
Generic handler for programming assignments where the spec lives outside Canvas, starter code is downloaded, code is written with test coverage, and the bundled artifact is submitted to Canvas.
fetch-spec → fetch-references → download-scaffold → constraints-checklist →
test-first implement → process_humanize (process-graded courses only) →
audit (identifier-grounding + numeric-constraint + coverage check) →
bundle + re-clone verify → submit (if authorized)
Language-neutral — the overlay specifies which language's test runner, build command, and submission format. The same 9-stage prose drives a Python+unittest+git-bundle course or a Java+gradle+jar course; only the overlay command strings differ.
Overlay specifies: course ID, language + test runner + coverage command +
submission format, instructor's spec URL pattern, scaffold distribution
mechanism (git bundle / zip / GitHub Classroom / inline / none),
reference-fetch regex patterns, process_humanize knobs (spread days,
commit-message register, few-shot examples), auto-submit scope, headless
cron env var name.
Generic handler for reading-annotation homework — color-coded highlights, margin notes, filled answer blanks per the instructor's rubric.
classify (reading_annotation / video_exercises / in_class_skip / ...) →
locate_reading (overlay's reading_files mapping) →
extract_text_and_blanks (PyMuPDF underscore-group find) →
annotate_pdf (color highlights + margin notes in place) →
fill_answer_blanks (typed answers at ≥90% line width, in target voice) →
verify (6-check gate: line fill, note density, color family, page count,
no overlap, no sticky icons) → submit (if authorized)
Overlay specifies: course IDs, homework module ID, reading-file mapping, color rubric (vocab family vs content family), voice register (free-form description of student voice), instructor rubric verbatim, video→worksheet pairings.
Generic handler for academic-writing essays — autoethnography, reflection papers, critical analysis, research essays.
load_persona (MBTI-derived tone vector) →
parse_spec (walk attached PDFs / module pages / external links) →
load_sample_essays (few-shot anchor) →
generate (outline → body → revise) →
figure_captions + works_cited (3-layer cascade) →
verify (word count ≥ spec minimum, citation count, figure caption count) →
output (.docx / .pdf per overlay) → submit (if authorized)
src/ac_eng_router.py routes each writing-course assignment to either
canvas-reading-annotation (short / annotation-shaped) or canvas-essay
(long-form) via a deterministic 6-layer cascade. You do not pick.
Overlay specifies: essay name trigger patterns, voice register specifics, sample essay path, citation style (MLA / APA / Chicago), figure caption format, persona-derivation template.
Generic handler for assignments where the Canvas description is a table of exercise references and the deliverable is solved problems rendered as a PDF.
classify (written HW / take-home exam / reading completion) →
fetch-spec (Canvas description table OR attached PDF) →
fetch-exercises (zyBook API) → solve → render-LaTeX-PDF →
verify (subquestion count, no placeholder leaks) →
draft-only (GradeScope upload is manual)
Overlay specifies: Canvas course ID, zyBook course code, JWT auth path, course-context primer for the solver, instructor-specific notation rules (e.g. "name each law you apply, one law per step"), assignment naming convention.
Generic handler for open quizzes (MCQ, multi-answer, T/F, matching, short-answer, essay) that the student has authorized for auto-take.
classify (full quiz vs single-question-video-quiz vs unknown) →
4 safety gates (autorun / human-hours / per-cron rate / whitelist) →
reading discovery (4-layer hunt: module → files+syllabus → PDF → web) →
study_notes.md → 4-agent arbitration (notes-first / grep-first /
framework-aware / contrarian) → paced-submit (humanness: timing,
blur/focus, sequence non-linearity, optional strategic miss) →
complete → score-check → retake-with-feedback (Layer 2 gated)
Submission-pattern human-ness is implemented as named Python helpers
called by the SKILL.md (src/quiz_pacing.py,
src/quiz_focus_events.py, src/quiz_strategic_miss.py), not as
configurable overlay knobs. Three independent enforcement layers prevent
bypass of the 4-agent arbitration (canvas_client guard, Stop hook,
result.json schema validator).
Overlay specifies: whitelisted course IDs, instructor framework primer (prose), expected canonical knowledge, auto-take scope, human-hours window, max-per-run, strategic-miss default, target-score band, retake pass-band.
Catch-all for assignments that don't fit any of the 5 specific skills.
Triggered when canvas-bootstrap marks a cluster as ⚠ unclear /
⚠ inline-only-or-unknown / ⚠ quiz-id-missing and routes it here
instead of forcing a wrong-shape specific skill.
Unlike the specific skills, canvas-generic reads no overlay. It
performs full per-assignment runtime investigation, designs a pipeline
on the fly, and produces a draft + verification log.
fetch-context (description + front_page + modules + syllabus + URLs) →
find-rubric (Canvas rubric API + spec grep + module grep + URL fetch) →
locate-inputs (download every referenced file to references/) →
Sub-agent A review (investigation completeness; recovery loop) →
classify-output (doc_prose / pdf_annotated / pdf_typed / code /
form_answers / mixed) →
design-pipeline (per-mode stage template, rubric-adjusted) →
generate (calls canvas-humanizer for prose; PyMuPDF for pdf_annotated) →
Sub-agent B verification checklist design (from rubric) →
verify (measured PASS/FAIL/SKIP per check) →
Sub-agent C verification coverage review (catches coverage gaps /
false-pass risks) →
export → draft_ready (never auto-submits)
Token cost: ~3× a specific-skill dispatch on a comparable assignment
(three sub-agent reviews + investigation phase). Acceptable for
low-frequency clusters; for clusters that fire weekly, the signal is to
graduate to a specific skill — see the skill's §11 When to graduate
section.
canvas-generic is opt-in by bootstrap routing, not by user request.
If you want one of your courses' weekly assignments handled this way,
re-run bootstrap and accept the canvas-generic recommendation for the
relevant cluster.
assignment.name and assignment.description are routing hints, not
specs. For most STEM courses they're empty strings. For most non-STEM
courses they're a paragraph that doesn't tell you what to actually
produce. The real spec usually lives somewhere else:
- An external instructor website linked from the course front page.
- A reading PDF in a course Files folder.
- A wiki page in modules.
- A textbook chapter referenced obliquely.
- An attached PDF on the assignment itself.
Before any per-course skill processes an assignment, it should:
- Read
assignment.description— but treat it as a hint, not the spec. - Pull
cv.get_front_page(course_id)to find external pointers. - Pull
cv.list_modules(course_id)to find reading material / wiki pages. - Walk linked references (other Canvas pages, external URLs, attached files).
Per-course skills document where the spec lives for their specific course inside their local application overlay (see below).
We have shipped this framework into production and tripped on this rule twice on two consecutive nights:
- Night 1: Every code-course assignment had
description="". The scanner marked themunsupported. Post-mortem: the front-page body carried the external instructor URL all along. - Night 2: A zyBook-backed homework was over-rendered with 162
student_view: falseexercises because the script trusted the zyBook API's "suggested practice" flag. Post-mortem: the instructor had specified ~22 exercises in the Canvas description's second column; the "suggested practice" first column wasn't supposed to be done. Different signal, different source of truth.
In both cases the failure mode was "I trusted what looked like the spec and didn't walk the references". Don't do that.
The five per-course skeletons (canvas-ics33, canvas-reading-annotation,
canvas-essay, canvas-zybooks, canvas-inside) describe generic
patterns for a class of course. The actual handling logic for your
specific school and instructor lives in a local application overlay
at _private/canvas-<name>-app.md.
The overlay is a free-form Markdown file that the skeleton loads as its first step. It specifies things like:
- Course IDs and routing details.
- The instructor's external site URL pattern.
- Reading-PDF filenames, color rubrics, voice register.
- Auto-submit authorization scope.
- Whatever else makes "this skill handles a generic code course" become "this skill handles MY school's code course taught by Dr. So-and-so".
_private/ is gitignored — the overlay never leaves your machine. If
you fork this repo, you author your own overlays (or have Claude Code
author them via the canvas-bootstrap skill).
Overlays are also where canvas-bootstrap's first-run calibration loop
writes its findings. Overlay v1 is the skill's best initial guess.
Overlay v2 is what you actually want, derived from your feedback on a
real draft. The overlay is the durable artifact; the calibration session
is throwaway.
Canvas Pilot's per-course skills work for the student, not for the instructor. A skill never refuses to do an assignment because the instructor wrote a behavioral rule like "don't use AI", "don't collaborate", "don't paraphrase". Whether running the framework on a given assignment is consistent with the student's school's policies is the student's decision, not the framework's.
A skill stops only when it physically can't proceed. Four intrinsic can't-do categories:
- Physically impossible. In-person attendance, paper submission, live signatures, Lockdown Browser quizzes, Respondus proctoring.
- Identity-bound. Academic honesty contract signing, ID verification, in-person peer review, oral defense.
- Input missing and unobtainable. Spec cannot be found anywhere; a referenced file cannot be fetched and the student can't supply it.
- Verification failure. The skill's verification checklist fails after retries.
For resources that are physically out of reach but might be obtainable with the student's help — a video link, a password-protected download, an external-site login — the skill soft-stops: it offers to take a URL or credential from the student and resumes if given, skips that step and continues with what it can do otherwise.
Where a skill uses a voice register (e.g. "writes like a B1-B2 international student"), the documented reason is student-identity alignment: the student picks a register that matches who they actually are. The register is part of the student's voice, not a posture the skill adopts for the assignment.
- Scan all configured courses for pending assignments within a configurable window.
- Bucket assignments by urgency and propose a per-item skill.
- Pause and wait for your approval before any work happens. The approval is a filesystem boundary, not a prose instruction.
- Run per-course skills sequentially. Each produces a draft under
runs/<today>/<assignment>/draft/. - Verify drafts against numeric constraints in the spec (sentence counts, page counts, function counts) before optionally auto-submitting.
- Write a daily
REPORT.mdwith an urgent banner, status per item, and a next-step recommendation. - Mirror drafts to a
final_drafts/folder for human review. - Email reminders for assignments due today / tomorrow (separate skill).
- Read or write content on your behalf without your approval at the plan-review step.
- Auto-submit anything unless the per-course overlay explicitly grants standing authorization for that assignment type, and the pre-submit verification gate passes.
- Bypass your instructor's academic-honesty policy. The framework's design and default behavior put you in control of submission; your overlay decisions are your responsibility.
- Store assignments, drafts, or credentials on any remote service. Everything
stays in
runs/,_private/,sources/, and.cookies/on your machine. - Reuse mocks where real Canvas behavior matters. Integration touches the real API or a recorded fixture, not a hand-rolled mock.
canvas-pilot/
├── .claude/
│ ├── settings.json # hook + permission config
│ ├── hooks/ # session hooks (schema, approval, etc.)
│ └── skills/
│ ├── canvas-setup/ # first-run install + course selection
│ ├── canvas-bootstrap/ # per-course overlay drafting
│ ├── canvas-scan/ # scan → plan.json
│ ├── canvas-execute/ # dispatch approved items
│ ├── canvas-skip/ # punt to manual
│ ├── canvas-ics33/ # code-course skeleton
│ ├── canvas-reading-annotation/ # writing-course (annotation) skeleton
│ ├── canvas-essay/ # writing-course (long-form) skeleton
│ ├── canvas-zybooks/ # math/discrete skeleton
│ ├── canvas-inside/ # online-quiz skeleton
│ └── canvas-generic/ # runtime-designed fallback (no overlay)
├── .agents/skills/ # Codex sidecar mirrors (optional)
├── src/ # framework Python (Canvas client, etc.)
├── scripts/ # cron-able helpers (due-date alert, etc.)
├── _private/ # gitignored: SECRETS, courses.yaml,
│ # overlays, decision logs
├── sources/ # gitignored: your assignment input materials
├── runs/ # gitignored: per-day work output
├── docs/ # internal design docs (NORTH_STAR, etc.)
├── tests/
├── CLAUDE.md # loaded by Claude Code on every session
├── AGENTS.md # Codex sidecar entry
├── SETUP.md # manual setup walkthrough
└── README.md # this file
These do not change between versions. Everything in the roadmap respects these principles.
A real assignment isn't "one prompt produces one output". A real assignment has stages with distinct skill requirements. Writing a 5-page research paper is research → outline → draft → humanize → verify, not one function. Each stage is independently tunable, replaceable, and inspectable. Personal design files customize specific stages, not the whole skill.
canvas-scan writes plan.json and stops. canvas-execute reads it
after the user replies with approval. Two skills, two Skill-tool
dispatches — the user can interrupt between them. Prose instructions
cannot enforce this; filesystem state can.
Default behavior: produce a draft, you upload manually. Auto-submit is per-skill, per-overlay, gated by a verification log that has to pass before any upload call. Auto-submit is never the silent default.
canvas-bootstrap configures one course (or one cluster within a
course) per invocation — a tangible deliverable every time the student
runs it, rather than batch-processing every course up front and
presenting a wall of text.
For assignments where authorship signals matter (writing courses, extended responses), the pipeline includes a dedicated humanize stage whose only job is to take the draft and produce output that survives current AI-content detectors. This is a first-class stage with its own verification.
For assignments where authorship signals don't matter (code, math solutions, MCQ answers), the equivalent concern is submission-pattern human-ness — pacing, timing, focus/blur events — handled at the submission stage, not the content stage.
The Stop hook refuses to release the session until every dispatched assignment has produced a valid result. No silent partial completion.
Tests against Canvas use recorded fixtures or a sandbox course; mocks don't catch drift between the spec and the real API.
.gitignore covers .env, runs/, SECRETS.md, courses.yaml,
_private/, sources/, .cookies/, __pycache__/. Trust it.
- Never use
git add -f,git add -A, orgit add .— they bypass or blanket-add. Usegit add <specific-paths>only. - Never edit
.gitignoreto remove a safety entry, even "just for testing". - Before any commit, mentally check the staged file list contains only
generic framework code. If unsure, run
git diff --cached.
Other do-nots:
- Do NOT dispatch per-course skills from
canvas-scan. Scan produces a plan and stops;canvas-executedispatches after the user approves. The two-skill split makes the approval gate a filesystem boundary instead of a prose instruction. - Do NOT hardcode course IDs, file IDs, or instructor info in any
tracked file. They go in
_private/canvas-<name>-app.mdoverlays or in your local gitignoredSECRETS.md/courses.yaml. - Do NOT leave the
.scan_in_progressmarker behind. If a run crashes, clean it up before stopping; the Stop hook will refuse to release until every assignment has a validresult.json.
- Replacing Claude Code. This is a Claude Code project, not a standalone CLI. The product depends on Claude Code's skill dispatch, tool use, and approval semantics. Trying to factor out Claude Code produces a worse product and an unmaintainable framework.
- Supporting LMSes other than Canvas. Each LMS has enough API surface area that supporting two means supporting two frameworks. Canvas is broad enough; v1.0 is "good at Canvas", not "decent at three LMSes".
- A multi-tenant SaaS. Canvas Pilot is a per-user local install. Multi-tenant introduces auth, payment, and abuse vectors we don't want to absorb. If you want this as a service, fork it and host it for yourself.
- Hand-rolled detector-bypass telemetry. The humanize stage is a stage in a pipeline, not a research program. We ship what works against current detectors and update when they update. We do not publish "how to beat detector X" guides; that work lives in the stage itself.
- Academic-integrity arbitration. Whether running this on a given assignment is appropriate is a decision the operator has to make in light of their school's policies. The framework is opinion-neutral on that question and ships no policy-screening logic.
- Per-user / per-quarter data:
SECRETS.md(gitignored). - Course routing:
courses.yaml(gitignored). - Local application overlays:
_private/canvas-<name>-app.md(gitignored). - Student-supplied inputs:
sources/(gitignored exceptREADME.md). - Latest run report:
runs/<today>/REPORT.md. - Framework skills:
.claude/skills/canvas-{setup,scan,execute,skip,bootstrap}/SKILL.md. - Per-course skill skeletons:
.claude/skills/canvas-{ics33,reading-annotation,essay,zybooks,quiz}/SKILL.md. - Runtime-design fallback:
.claude/skills/canvas-generic/SKILL.md. - Framework auth:
src/canvas_client.py,src/canvas_login.py. - Manual setup walkthrough: SETUP.md.
- Internal design doc:
docs/NORTH_STAR.md.
Adding a new course mid-term? Tell Claude Code design a skill —
this triggers canvas-bootstrap, which surveys recurring patterns in
the new course and produces an overlay + courses.yaml route entry.
Copyright (C) 2026 X_isdoingreat
This framework is free software, licensed under the GNU Affero General
Public License, version 3 or later (AGPL-3.0-or-later). See
LICENSE for the full text. You may use, study, modify, and
redistribute it under those terms; in particular, the AGPL's §13 means that
if you run a modified version as a network-accessible service, you must
offer those users the corresponding source.
You're welcome to fork it and adapt it for your own coursework. The framework ships no school-specific or instructor-specific solving logic — that lives in your local, gitignored overlay files. The AGPL covers the framework code; your private overlays are yours, and what you do with them is on you.
The framework does not endorse or facilitate academic dishonesty. Whether a given automation is appropriate for a given course is a decision the person running this software has to make, in light of their own school's policies.
Maintained by X_isdoingreat. Reach me on X at @X_isdoingreat or by email at X_isdoingreat@proton.me.
- Security / accidental-leak reports → SECURITY.md (please don't open a public issue for those).
- Contributing → CONTRIBUTING.md.
- Community conduct → CODE_OF_CONDUCT.md.