The CLI tool for Mincong Classroom (mc). It's mainly for auto-grading assignments 🤖
There are about 25-30 students in a classroom each year. If they group into teams of two members, there are tens of repositories. It is hard to manage without a specific tool. In particular, all the Git repositories have the same structure because they are cloned from the same template mincong-classroom/containers. This is challenging for updating the repository, printing the content of a specific file, etc.
It's a bit difficult to run all the team specific commands manually. I could do that with a for-loop but inside that for-loop, there are some variables that need to be computed, all of which are team-specific. For example, the Git repo name, the path of a specific file, ...
Install Golang:
brew install go
go version
# go version go1.24.1 darwin/arm64Then build the CLI:
go mod tidy
go build -o dist/mcmc helpMincong Classroom (mc) is a command line interface for grading student
assignments in the Kubernetes course.
Usage:
mc [command]
Commands of the teacher:
eval Evaluate the labs, then list, check and export their evaluations
git Perform Git operations in bulk
info Display CLI information
k8s Perform Kubernetes operations
rule List, check and render the evaluation rules of the year
sheet Set up the evaluation spreadsheet, which imports the export of mc eval export
team Manage the teams
Commands of the agents:
annotate Find a text in a screenshot, then draw numbered boxes around what the evaluator found
Additional Commands:
completion Generate the autocompletion script for the specified shell
help Help about any command
Flags:
-h, --help help for mc
--team-file string Team registry to use instead of ~/.mc/teams-{year}.yaml, e.g. a test registry to try the commands
The settings of mc on this machine are in ~/.mc/settings.yaml. The file and each of its keys
are optional:
year: 2026 # the year of the cohort
rules: ~/github/my-org/course/{year}/evaluation-rules.yaml # the evaluation rules of each year
agents: # the AI agents of mc eval run
report:
run: claude -p "/report-lab {lab} {teams}"
inputs: [.claude/skills/report-lab]
grade:
run: claude -p "/grade-lab {lab} {teams}"
inputs: [.claude/skills/grade-lab]The year selects the files of the cohort, such as the team registry ~/.mc/teams-{year}.yaml. It
is the environment variable MC_YEAR if set, e.g. MC_YEAR=2025 mc rule ls, then the key year,
then the current year. rules is the YAML of the evaluation rules, where {year} is the year:
mc rule and mc eval read it. agents are the AI agents of the steps of mc eval run, see
Evaluation. An unknown key is an error, so that a typo doesn't go unnoticed. The
former key agents.eval_run is an error too, which says what to write instead.
The teams are registered in a private YAML file, the team registry ~/.mc/teams-{year}.yaml.
A team has a name, lowercase letters only (such as a color), and at most two members. A team
can have no members yet: it is created before the course, and its members are added later.
A name is written "LAST First": the last name in upper case, then the first name in Pascal case,
each of one or more words, e.g. "DE LA FONTAINE Jean-Pierre". A member whose name does not follow
this format is a warning; the names are compared ignoring the case and the extra spaces.
The optional students list holds the students of the year, known before the course. It is
informational: mc team ls lists the students who are not in a team yet, and a member who is not
among them is a warning, shown by provision before its confirmation. For a member, only the
GitHub user must be valid.
The optional evaluation_sheet is the URL of the spreadsheet where the teams of the year are
evaluated. mc does not read the spreadsheet: mc info prints its URL, so that it can be found
without opening the registry, which holds the names of the students.
evaluation_sheet: https://docs.google.com/spreadsheets/d/{id}/edit
students:
- name: "SMITH John" # "LAST First"
- name: "DOE Jane"
teams:
- name: red
members:
- name: "SMITH John" # as in the students
github: jsmith # GitHub username
- name: orange
members: [] # not taken yetThe year of the registry is the one of the settings: see "Settings".
The global flag --team-file replaces the team registry for any command, e.g. to rehearse with a
test registry before the course. provision still acts on the real organization: add --dry-run.
mc team ls --team-file ~/.mc/test-teams-2026.yaml
mc team yellow provision --dry-run --team-file ~/.mc/test-teams-2026.yamlEach team gets a private repository k8s-{team} in the GitHub organization, generated from the
template repository mincong-classroom/containers, and a secret GitHub team {team} with push
access to it. With the organization's base permission set to none, the members of a team only
see their own repository. The GitHub calls go through the gh CLI, logged in with the scopes
repo and admin:org:
gh auth refresh -s admin:orgThe commands are mc team ls, and mc team <team> <action> for the actions on one team of the
registry: each team is a subcommand. mc team --help lists the teams with their members, and
mc team red --help the actions on the team red. A team that is not in the registry yet has one
action, provision, which registers it. The names ls and provision are reserved.
| Command | What it does |
|---|---|
mc team ls [--json] |
Lists the teams with their members, their status on GitHub and their validation problems, then the students not in a team yet |
mc team red provision [--dry-run] |
Shows the members of the team with their GitHub accounts, and once confirmed, creates its repository and its GitHub team, grants the team push access, and adds the members, which invites them to the organization. The team can be new: it is registered |
mc team red validate [--json] |
Errors, which prevent the provisioning: an invalid or reserved name, a name used by another team, a member without a GitHub username or whose GitHub user does not exist. Warnings, confirmed when provisioning: more than 2 members, a member in several teams or not among the students. Prints the display name of each GitHub account, for the students to confirm it |
mc team red status [--json] |
Shows whether the repository and the GitHub team exist, the access of the team to the repository, and whether each member is active or pending (invitation not accepted yet) |
provision asks one question: it shows the team, each member with the display name of their
GitHub account, and the warnings of the validation, then asks to confirm with y ("no" is the
default). What it takes to provision the team is not asked: once confirmed, it runs the steps that
are not done yet, one after the other, so a team can be provisioned again. A team with errors is not
provisioned, and nothing is asked. ctrl+c stops the command. With --dry-run, the same questions
are asked, and the steps are described with the gh command each one would run (in dark yellow in
a terminal, unless NO_COLOR is set), but nothing changes.
The members come from the registry, or are asked first:
- a team that is not in the registry yet gets its members, and is registered once confirmed;
- a registered team without members, such as a team created before the course, gets them too.
The members are picked by number among the students of the registry who are not in a team yet, or
typed when there is none to pick, each with their GitHub username (checked right away, and its
display name shown). The registry is updated once confirmed, keeping its comments, except with
--dry-run.
$ mc team purple provision
The team purple is not in the registry yet.
Students not in a team yet:
1. DOE Jane
2. MARTIN Alex
Members, by number, e.g. "1 2" (empty for none yet): 1 2
GitHub username of DOE Jane: jdoe
@jdoe: "Jane Doe" on GitHub
GitHub username of MARTIN Alex: amartin
@amartin: "Alex Martin" on GitHub
== Team purple: 2 members
- DOE Jane (@jdoe): "Jane Doe" on GitHub
- MARTIN Alex (@amartin): "Alex Martin" on GitHub
Provision the team purple? (y/N): y
✓ the team purple saved to ~/.mc/teams-2026.yaml
✓ Create the private repository mincong-classroom/k8s-purple from the template mincong-classroom/containers
✓ Create the secret GitHub team purple
✓ Grant the GitHub team purple push access to k8s-purple
✓ Add @jdoe to the GitHub team purple, which invites them to the organization
✓ Add @amartin to the GitHub team purple, which invites them to the organization
Team "purple" provisioned.
- repo: https://github.com/mincong-classroom/k8s-purple
- team: https://github.com/orgs/mincong-classroom/teams/purple
The commands mc git run git in bulk, in the repository of each team of the registry, cloned
under ~/github/mincong-classroom/k8s-{team}.
| Command | What it does |
|---|---|
mc git clone |
Clones the repository of each team |
mc git pull |
Pulls the latest changes of each team, by fast-forward only: see below |
mc git show <rev>:<path> |
Shows a file of each team, e.g. mc git show main:k8s/lab-2/pod-nginx.yaml |
mc git exec -- <git arguments> |
Runs any git command for each team, e.g. mc git exec -- log -1 --oneline |
exec passes the arguments after -- to git as they are, and prints the name of the team before
its output. When git fails for a team, it goes on with the next ones, then fails at the end,
listing these teams.
mc git pull only fast-forwards (git pull --ff-only): the history of a team is never merged
into the local one. When a team rewrote the history of its branch, e.g. with a force-push, the
local branch is no longer an ancestor of the remote one: the repository of that team is not
pulled, a warning says so, and the other teams are pulled.
[team: red] ⚠ history rewritten: force-push? The local branch is no longer an ancestor of the remote one: not pulled
The local clone keeps the history it had: compare it with the new one
(git log main...origin/main) before resetting it (git reset --hard origin/main).
The evaluation rules of a program are in a YAML file with two maps: labs, the lab sessions of
the module with their deadline, and rules, one entry per rule keyed by its ID, such as L1_DKF.
mc lists the rules of the year, checks them, and renders them as a Markdown page:
mc rule ls -l L1 # the rules of Lab 1
mc rule check # one problem per line, exit 1 if any
mc rule render -o evaluation-rules.md # the page, wrapped at 96 charactersThe file of the year comes from the key rules of the settings, ~/.mc/settings.yaml, where
{year} is the year (see "Settings"). The flag --rules-file replaces it, e.g. with a test file:
mc rule ls --rules-file rubric/testdata/evaluation-rules.yaml.
The check covers the keys, the IDs of the labs and of the rules, the exercises, the coefficients (by steps of 0.05, and those of the mandatory rules of a lab add up to 1), the penalties, the levels of analysis, the kinds of evidence, the dependencies, the deadlines, the paths, and the width of the lines: 120 characters at most. A problem gives the line of the file:
evaluation-rules.yaml:142: L1_DKF: unknown key "penalty"
The made-up file of the tests shows the format,
rubric/testdata/evaluation-rules.yaml, and its page,
rubric/testdata/evaluation-rules.md.
mc eval export writes the rules to the tab "Rules" of the evaluation sheet, where the tab
"Evaluation" looks up the score of each rule: see Evaluation and Sheet. A
row is found by its "Lab Session" and its "Rule", the end of the "Rule ID". A new rule gets a new
row, with the formulas of the tab. A rule that the file no longer has is listed, and its row is
never deleted. The README of evaluation gives
the columns.
mc eval gather-facts gathers the facts of each rule of a lab, for each team, in a file for the
teacher. They are facts, not a score, of 3 kinds:
- What the team wrote in its report for the exercise of the rule, without the lines of the statement.
- The files of the repository: those of the rule, and those the report links to. A short file is imported, a file of the template shows its changes, and a long one is linked.
- The evidence that the evaluator made: its notes and its files, such as screenshots and logs, next to the report, and the tags of the images of the rule on Docker Hub, with their push dates.
mc eval gather-facts -l L1 --template ~/github/mincong-classroom/containers
mc eval gather-facts -l L1 -t red --late # origin/main, for a late pass
mc eval gather-facts -l L1 -t red --force # gather again, even when the report is up to dateThe rules are those of the year, from the settings, or the file of the flag --rules-file. The
commit evaluated is the head of main at the deadline of the lab, from the activity of the
repository on GitHub. The facts of docs/lab-1.md are docs/lab-1.facts.md, and the files of
the evaluator go to docs/lab-1.evidence/: its notes for a rule are L1_DKF.md, and its files
l1-dkf-*, such as l1-dkf-build.log. Run the command again once they are there.
The facts go to the local branch teacher of the clone of the team, checked out in a worktree
under ~/.mc/{year}/teacher/, one directory per team: open it in an editor to read the facts next
to the files of the team. The branch starts from the commit evaluated, the next pass merges the new
one into it, and each change of the facts is committed. mc never pushes it.
The evaluator writes a report from the facts, such as a report for the teacher in
docs/lab-1.report.md. The front matter of the facts holds 2 commits: the commit of the team
evaluated, and the commit of mc that gathered the facts, which mc info prints. Then come the
facts of each rule that the evaluation sheet shows: the tags of its images on Docker Hub, each with
the first 12 characters of its digest, and the number of screenshots of the team in the section of
its exercise:
---
commit: c028aea876b2754fb24541d0ea89f90a06eda946
mc: 949d7aa740ab
rules:
L1_DKF:
screenshots: 2
L1_IMG:
images: "1.0.0 b80767bd1a2c, 1.1.0 0f3e5d6c7b8a" # "none" when Docker Hub has none of them
screenshots: 1
---The report copies this front matter. While both commits and the images stay the same, nothing
changed since it was written: mc eval gather-facts skips the team, and says that its report is up
to date. An image pushed again, without a new commit, makes a new pass. The flag --force gathers
the facts again anyway, e.g. after a new version of the report. A local build of mc with changes never skips a team, since its commit
doesn't tell which code ran.
The files had other names before: docs/lab-1.evidence.md for the facts, and
docs/lab-1.generated.md for the report. The next pass renames them with git mv, so that they
keep their history, and commits the renames. The check above then finds the report under its new
name, and the link of the report to the facts follows them.
mc eval is the command of the teacher for the evaluation of a lab, and mc rule the one for
its rules. mc eval run runs the steps of an evaluation, so that one command gives the facts, a
report and a draft of the grades of each team:
- The facts:
mc eval gather-facts, see Facts. - The report: an AI agent writes the report of each team from its facts, such as
docs/lab-2.report.mdon its branchteacher. It replays the work of the team for the evidence that is missing, and puts boxes on the screenshots withmc annotate. - The grades: an AI agent evaluates each rule, and writes the evaluation of each team.
- The checks of the evaluations, which are never skipped.
An agent is a command line of the settings, run with sh in the Git repository of the rules, where
{lab} is the lab, such as L2, {teams} the teams whose step runs, separated by spaces, and
{late} is late with --late, empty otherwise. Its inputs are the files and the directories
that it reads besides the facts, such as its skill, relative to that repository:
agents:
report:
run: claude -p "/report-lab {lab} {teams} {late}"
inputs: [.claude/skills/report-lab]
grade:
run: claude -p "/grade-lab {lab} {teams} {late}"
inputs: [.claude/skills/grade-lab]The agent of the grades writes what it found for each team in a YAML file, one per team and lab,
so that several agents can work at once: ~/.mc/{year}/evaluations/lab-{n}/{team}.yaml. Then
mc eval run checks these files against the evaluation rules, before they become the rows of the
evaluation sheet, and mc eval ls lists them. Here is the file of a made-up team, teal, in Lab 2:
team: teal
commit: 0a1b2c3 # the commit evaluated, the one of the facts
time_spent: Session only, done at 11:21
action_items: Run it yourself # or Talk with them, Less attention, Late work to decide
rules:
L2_POD:
completeness: 0.8 # from 0 to 1, by steps of 0.1
done_well: The Pod runs the image of Lab 1.
to_improve: The report doesn't show the logs of the Pod.
to_check: "" # what the teacher should check, if anything
analysis: short # a key of analysis_levels in the rules
writing_style: own words
penalty: null # or {kind: secret, remove: 0.3, why: The report shows a token.}The comment of the sheet joins done_well and to_improve, so that it always says what the team
did well and what it can improve: the check asks for both. To list the files, one line per team
and lab:
mc eval ls # every lab, in the order of the rules
mc eval ls -l L2 # one lab
mc eval ls --dir DIR # the files of DIR/lab-{n}/Each line gives the commit evaluated, the mandatory rules evaluated out of those of the lab (and
the optional ones after a +), the rules that the teacher should check, the penalties and the
action items:
LAB TEAM COMMIT RULES TO CHECK PENALTIES ACTION ITEMS
L2 coral 0a1b2c3 1/1 +1 1 1 Talk with them
L2 teal 0a1b2c3 1/1 0 0 Run it yourself
A file that can't be read is listed with dashes, and the standard error says to run mc eval run
for its problems. To evaluate a lab, then check its files:
mc eval run -l L2 # every team of the registry, then the checks of their files
mc eval run -l 2 -t teal # one team
mc eval run -l 2 --skip report # the facts and the grades, with the reports as they are
mc eval run -l 2 --force grade # the grades again, even when their inputs didn't change
mc eval run -l 1 --late -t teal # a late pass: the facts of origin/main
mc eval run -l 2 --template DIR # the facts with the template of the team repositories in DIR
mc eval run -l 2 --check-only # the checks only, without the steps
mc eval run -l 2 --dir DIR # the files of DIR/lab-2/The flags --skip and --force take a step, facts, report or grade, and repeat.
A step is costly to run again: the report replays the work of each team with Docker and kubectl,
and the grades read every answer. So a step runs for a team only when one of its inputs changed
since it last succeeded, or when its file is missing. mc records the digests of the inputs, in
SHA-256, in ~/.mc/{year}/evaluations/lab-{n}/.steps/{team}.json:
| Step | Its inputs | It succeeds when |
|---|---|---|
| The facts | The commit evaluated, the commit of mc and the images on Docker Hub, as mc eval gather-facts compares them with the front matter of the report |
The facts are gathered |
| The report | The front matter of the facts, without the commit of mc, and the inputs of its agent |
The agent wrote the report at the commit of the facts |
| The grades | The same, the report, and the rules of the lab without their coefficients | The evaluation passes the checks |
The commit of mc is left out of the inputs of the report, so that a new version of mc that
gathers the same facts writes no new report, and so is the body of the facts, which changes with
each push after the deadline. The coefficients are left out of the inputs of the grades: the
teacher tunes them after the grades, and they change no completeness. Here is a run after a change
of the skill of the report, whose new version writes the same report:
[team: teal] facts: 0a1b2c3, the head of main at the deadline: .../docs/lab-2.report.md, up to date with mc 949d7aa740ab (--force facts to gather again)
[team: teal] report: to run, .claude/skills/report-lab/SKILL.md changed
[team: teal] report: done at 0a1b2c3
[team: teal] grade: up to date
1 file of L2 checked in ~/.mc/2026/evaluations/lab-2: no problem
A team whose step fails stops there, and the others go on: mc eval run exits with 1 at the end.
A failed step records nothing, so the next run tries it again, and a failed agent stops every team
of its step. A team stops at the facts too when Docker Hub didn't give the images of a rule, so that
no report says that an image is missing. A record lost, such as ~/.mc cleaned, runs every step
again: safe, but costly.
The agents run with the variable MC_EVAL_AGENT set: mc eval run refuses to start another agent
from it, so the agent checks its own files with --check-only. With claude -p, a tool that the settings of Claude Code don't
allow is refused, since nobody is there to approve it: allow the commands of the skill in its
settings, or pass the options of claude in the command line of the agent.
The rules come from the settings, or the file of the flag --rules-file, as for mc rule. Each
mandatory rule of the lab must be there once, and an optional rule may be absent. The commit must
be the one of the facts that mc eval gather-facts gathered for the team: the commit in their
front matter, such as ~/.mc/{year}/teacher/k8s-teal/docs/lab-2.facts.md. A short hash matches
the full one. Without facts, the check says to run mc eval gather-facts first.
A penalty removes a share of the score of a rule. Its kind is one of 3:
- a penalty of every rule, by its name in
scoring.penalties, such assecret:removeis the share that the rules give rule, the penalty of the rule itself, when the rule has one:removeis one of its shareslate, for work pushed after the deadline:removegoes from theminto themaxofscoring.late_penalty
A problem gives the file and its line, and the command exits with 1 if there is any, like mc rule check:
/Users/me/.mc/2026/evaluations/lab-2/coral.yaml:8: L2_POD: unknown key "to_chek"
/Users/me/.mc/2026/evaluations/lab-2/teal.yaml:7: L2_POD: the completeness 0.85 is not from 0 to 1 by steps of 0.1
/Users/me/.mc/2026/evaluations/lab-2/teal.yaml:9: L2_POD: no to_improve: what the team can improve, which the comment of the sheet ends with
/Users/me/.mc/2026/evaluations/lab-2/teal.yaml:13: L2_POD: the penalty secret removes 0.3 of the score: got 0.2
Error: 4 problems in 2 of 2 files
A file with an unknown key, a key twice or a value of the wrong type stops at these problems: the other checks of the file run once they are fixed. Without a problem, the command prints how many files it checked.
Once the files have no problem, mc eval export writes them to the export of the evaluation
sheet, ~/.mc/{year}/sheet/export.json, with the rules, and copies it to the clipboard. mc has
no access to the spreadsheet: a script of the spreadsheet imports the export, see Sheet.
mc eval export # every lab with evaluations in ~/.mc/{year}/evaluations/
mc eval export -l L2 -t teal # one lab and one team: the other rows stay as in the last export
mc eval export --sheet-dir DIR # export.json in DIR, instead of ~/.mc/{year}/sheet/The export checks the files first, as mc eval run --check-only does. Then it starts from the last
export, and replaces the cells of mc with the files:
- a row is found by its team, its lab and its rule. A new row comes after the last one, with the formulas of "Rule Score", "Score" and "TA Lab Flag"
- every TA column follows the files: the "TA Completeness", even when it is lower than in the last export, with its "TA Comment": "Done well: ... To improve: ...". A text that the file leaves empty clears its cell
- "TA Docker Images" and "TA Screenshots" follow the facts of
mc eval gather-factsthe same way: the tags of the images of the rule, and the number of screenshots of the team for its exercise - a penalty becomes the formula of "TA Penalty", such as
=-0.1*IF(ISBLANK(H{row}),G{row},H{row})*F{row}, and its reason goes to the "TA Comment". Without a penalty, the cell is cleared - the "Score" takes the completeness of the teacher, in the column H, once the teacher fills it. A filled H also means that the teacher reviewed the evaluation of the rule and confirmed it: it is final, so the import never writes its row, see Sheet
So the sheet shows what the files say: a new rule, a new judgment or a fix reaches it, even when it
lowers a completeness. The export prints each cell that changed since the last export, then the new
rows, and counts the completenesses that went down: check them before the import. For a made-up
team teal, whose second pass gives a higher completeness to a rule and a lower one to another,
and a new team coral:
$ mc eval export -l L2
Exported 4 rules and 4 rows of the tab Evaluation to /Users/me/.mc/2026/sheet/export.json, from 2 evaluations of L2.
TAB ROW COLUMN CHANGE
Evaluation teal L2 L2_POD TA Completeness 0.6 -> 0.8
Evaluation teal L2 L2_POD TA Comment "Done well: The Pod runs. To improve: Sho..." -> "Done well: The Pod runs the image of Lab..."
Evaluation teal L2 L2_LOG TA Completeness 1 -> 0.5, lower
Evaluation teal L2 L2_LOG TA To Check nothing -> "The logs of the second container are mis..."
1 completeness lower than the last export, to check before the import.
New 2 rows of the tab Evaluation: coral L2 L2_POD, coral L2 L2_LOG.
Copied the export to the clipboard, 5 KB.
Next, in the spreadsheet: the menu "mc", then "Import", paste it in the text box, then Import.
The first time, add the script of mc sheet script: "Extensions", then "Apps Script".
mc annotate puts numbered boxes on a screenshot, around what the evaluator found: a green box
validates something, and a red box is a flag. The text next to the image refers to each box by
its number. It takes 2 steps: find reads the screenshot with
tesseract and prints the box of each line that
contains a text, then draw copies the screenshot with the boxes:
brew install tesseract # for mc annotate find only
mc annotate find build.png "BUILD SUCCESS"
# 120,40,300,22 [INFO] BUILD SUCCESS
mc annotate find build.png --all # every line, to choose another text
mc annotate draw build.png build-boxed.png 120,40,300,22,green,1 40,90,210,22,red,2A box is written x,y,w,h,COLOR,LABEL: the box that find prints, green or red, and its
number. The border is thick, with a padding around the text and no fill, and the number is in a
circle of its color, on the right side of the box: the image is widened when a box ends too close
to its right edge. The search of find ignores the case, the spaces and the kind of dash, since
tesseract often splits a word in two and reads a long dash as a hyphen.
By default, a gray band at the bottom of the copy says that the screenshot is the team's, and
that the evaluator added the boxes. The flag --evaluator leaves it out, for a screenshot of the
evaluator, which carries its own watermark. The flag --padding sets the space between a text and
its box, e.g. --padding 1 when the box crosses the lines around it.
The evaluation spreadsheet imports the export of mc eval export with a script of its own, written
with Apps Script. mc never calls the API of Google
Sheets, so it needs no project of Google Cloud and no token: the teacher, or an agent in the
browser, pastes the export in a dialog of the spreadsheet. To add the script, once per
spreadsheet:
mc sheet script --copy # or mc sheet script, to print itPaste it in "Extensions", then "Apps Script", in place of the code there, save it, then reload the spreadsheet. The menu "mc", then "Import", opens a dialog with a text box: paste the export, then click "Import". The first import asks to authorize the script, for this spreadsheet only.
The export holds one object per tab, and one object per row, like the documents of a collection in MongoDB: each attribute is a column, named by its header. Here is the tab "Evaluation" of an export, with a made-up team, and some columns only:
{
"spreadsheet": "0123456789",
"tabs": [
{
"name": "Evaluation",
"keys": ["Team", "Lab", "Rule"],
"columns": {"A": "Team", "D": "Lab", "E": "Rule", "F": "Rule Score", "G": "TA Completeness", "I": "Score", "M": "TA Commit", "V": "TA Comment"},
"formulas": {"Rule Score": "=VLOOKUP(E{row},Rules!C:F,4,FALSE)", "Score": "=IF(ISBLANK(H{row}),G{row},H{row})*F{row}+T{row}"},
"fill": ["B", "C"],
"confirmed": {"H": "Completeness"},
"rows": [
{"Team": "teal", "Lab": "L2", "Rule": "L2_POD", "TA Completeness": 0.8, "TA Commit": "0a1b2c3",
"TA Comment": "Done well: The Pod runs the image of Lab 1. To improve: Show the logs of the Pod."}
]
}
]
}As you can see:
columnsis the range ofmcin the tab: the letter of each header. The other columns are never written, such as B and C, which hold the students, and H, J and K, which are the teacher's- the keys, "Team", "Lab" and "Rule", find a row
formulasgives the formula of a column on every row, where{row}is the number of the rowfillnames the columns that a new row copies from the row above, such as the formulas that find the students of its teamconfirmednames the column that confirms a row: H, the completeness of the teacher. A filled H means that the teacher reviewed the evaluation of the rule, and it is final: the import never writes that row. The script reads this column, but never writes it- a row holds the values of its cells, and a cell not given becomes empty: the export holds the
whole range of
mc, not the changes spreadsheetis the ID of theevaluation_sheetof the team registry: the script refuses an export for another spreadsheet
The import runs in 4 steps, for each tab:
- It reads the headers of the first row, the columns of the keys and the confirmed column. When a header of the export is not at its letter, it stops before any write.
- It finds the row of each key, and gives a new key a new row at the end, whose
fillcolumns it copies from the row above. - It writes the columns of
mcon every row but the confirmed ones, with one call per group of adjacent columns and run of rows. A text that Sheets would read as something else, such as the commit2e88112(a number), starts with an apostrophe, which keeps it a text. - It reads the cells back, compares them with the export, and shows a summary: the rows imported, the new ones, and each cell that differs. It also lists the confirmed rows, with their cells that differ from the export, left as they are.
A row of the tab whose keys are not in the export keeps its keys, and its other cells of mc
become empty, unless it is confirmed: the summary lists it, to delete by hand.
The cmd directory wires the commands of the command line interface with Cobra: its
README explains how a command runs, and how mc eval run starts an agent.
The rubric directory reads the YAML of the evaluation rules: it checks the file, and renders its
page from the template rubric/page.md.tmpl.
The evidence directory gathers the facts of a lab for a team, on the local branch teacher of
its clone: its README shows where the facts come from.
The evaluation directory reads the evaluation of a lab for a team, as the evaluator writes it,
checks it against the evaluation rules, and computes the export of the evaluation sheet: its
README shows what a check reads, and what an export holds.
The annotate directory finds a text in a screenshot with tesseract, and draws numbered boxes on
it.
The sheet directory holds the export of the evaluation spreadsheet, and the script of the
spreadsheet that imports it: its README explains why, with a diagram.
The github directory manages the GitHub organization (repositories, GitHub teams and their members) through the gh CLI.
The common directory holds what the commands share: the settings, the year, the team registry
and the commit of mc. Its README draws its types, and the packages that use
them.
The e2e directory runs the binary of mc end to end, in a home of its own and with a fake gh:
its README explains how.
The .mc directory is private. It contains the team registry .mc/teams-{year}.yaml, the settings, and the files of each year in .mc/{year}/, such as the evaluations. This directory is ignored by Git.