Skip to content
mincong-classroomPublic

About

The CLI of Mincong Classroom (mc). It serves as a Teaching Assistant by managing team repos, gathering facts for grading, and more 🤖

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

mc

The CLI tool for Mincong Classroom (mc). It's mainly for auto-grading assignments 🤖

Problems

Managing Git Repositories

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.

Running Team Specific Commands

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, ...

Installation

Install Golang:

brew install go

go version
# go version go1.24.1 darwin/arm64

Then build the CLI:

go mod tidy
go build -o dist/mc

Usage

mc help
Mincong 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

Settings

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.

Team

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 yet

The 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.yaml

Each 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:org

The 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

Git

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).

Rule

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 characters

The 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.

Facts

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:

  1. What the team wrote in its report for the exercise of the rule, without the lines of the statement.
  2. 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.
  3. 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 date

The 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.

Evaluation

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:

  1. The facts: mc eval gather-facts, see Facts.
  2. The report: an AI agent writes the report of each team from its facts, such as docs/lab-2.report.md on its branch teacher. It replays the work of the team for the evidence that is missing, and puts boxes on the screenshots with mc annotate.
  3. The grades: an AI agent evaluates each rule, and writes the evaluation of each team.
  4. 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.

Each Step When Its Inputs Change

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 as secret: remove is the share that the rules give
  • rule, the penalty of the rule itself, when the rule has one: remove is one of its shares
  • late, for work pushed after the deadline: remove goes from the min to the max of scoring.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-facts the 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".

Annotate

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,2

A 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.

Sheet

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 it

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

  • columns is the range of mc in 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
  • formulas gives the formula of a column on every row, where {row} is the number of the row
  • fill names the columns that a new row copies from the row above, such as the formulas that find the students of its team
  • confirmed names 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
  • spreadsheet is the ID of the evaluation_sheet of the team registry: the script refuses an export for another spreadsheet

The import runs in 4 steps, for each tab:

  1. 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.
  2. It finds the row of each key, and gives a new key a new row at the end, whose fill columns it copies from the row above.
  3. It writes the columns of mc on 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 commit 2e88112 (a number), starts with an apostrophe, which keeps it a text.
  4. 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.

Key Components

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.

About

The CLI of Mincong Classroom (mc). It serves as a Teaching Assistant by managing team repos, gathering facts for grading, and more 🤖

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages