How query-plays.mjs works
This is the one program the agent is allowed to run. It reads one file, throws out the plays the filters exclude, counts what is left, and prints both the count and every filter it used. It is short enough to read in one sitting, and it was written before the agent existed.
What it is written in
.mjs ending means it is a JavaScript module: a file that brings in other code with import.node ./query-plays.mjs and then the filters. It was built and tested on Node 24, and the daily refresh on GitHub runs it on Node 22.node:fs to read a file, and node:path and node:url to find the data folder next to the script. There is no package.json and no npm install.What happens when it runs
Six stages, top to bottom, the same every time. The left column is what the code does. The right column is what that looked like for one real command, node ./query-plays.mjs --seasons 2021-2025 --half 1, on October 11, 2026.
Read the command
It reads the filters typed after its name and checks each one. A filter it does not know, or a value that makes no sense, stops it right there with a message and exit code 64. It never guesses what you meant.
Lines 41 to 87
In the example
--seasons 2021-2025 five seasons --half 1 first half only everything else left at its default
Load the plays
It reads data/plays-slim.csv into memory, one row per play, and splits each row at the commas. This is the only file it opens, and it only reads it.
Lines 89 to 96
In the example
258,611 plays 18 columns each, of the 372 in the raw data no betting column among them
Apply the filters, in order, and write each one down
Nine filters, always in the same order. After each one it records the filter's name and how many plays are left, even when the filter removed nothing. That record is the list it prints at the end.
This is the stage the whole design rests on: a filter cannot be applied without also being written down, because one small function does both.
Lines 98 to 143
In the example, plays left after each
- 0. The file258,611
- 1. Seasons 2021 to 2025247,284
- 2. Regular season and playoffs247,284
- 3. All weeks247,284
- 4. Only rushes and passes179,045
- 5. No kneel-downs, no spikes176,491
- 6. First half only87,209
- 7. Any score87,209
- 8. Any score at halftime87,209
Add the plays up, one team in one game at a time
For each team in each game it counts rushes and passes. Rush share is rushes divided by rushes plus passes. It notes whether that team won, using the final score. Tied games are dropped, and so is any team-game with fewer than 10 plays left.
Lines 145 to 165
In the example
1,424 games 4 tied games dropped 0 team-games dropped for too few plays 2,840 team-games kept
Sort, cut into equal groups, and count the wins
It sorts the team-games from the highest rush share to the lowest and cuts them into four groups of the same size. For each group it counts the wins and divides. The gap is the top group's win rate minus the bottom group's. If a group has fewer than 30 team-games, it adds a small-sample warning.
Lines 167 to 187
In the example
4 groups of 710 ran most 376 wins of 710 53.0% ran least 327 wins of 710 46.1% gap 6.9 points
Print the filters first, then the result
It prints the question, the filter list from stage 3, the team-game counts from stage 4, the table from stage 5, and one standing reminder: a gap is an association, not a cause. With --json it prints the same content as data, which is how the explorer page gets its answers.
Lines 189 to 224
In the example
QUESTION FILTERS, IN ORDER (plays left after each) THEN, PER TEAM PER GAME RESULT NOT IN THIS OUTPUT
The agent reads that printout and nothing else. It never sees a play.
What it never does
These are properties of the code, not promises in a prompt. You can confirm each one by reading the file.
The runner: run-analyst.sh, in six beats
The tool above is what the agent calls. The runner is what you call: a shell script of about 120 lines that hands the agent one claim and makes its work visible. Top to bottom, it does six things.
claim.txt when you give none. A claim with a quotation mark inside it goes in the file.analyst.md and passes it to claude -p as the system prompt. The policy is the only thing written for this show; the model is unchanged.Bash(node ./query-plays.mjs:*). This exact script, any arguments, and nothing else: no file reads, no writes, no network. The policy says it; this line enforces it.show-run.mjs, which prints each tool call as it lands, with the filters and the result, and saves the ruling under rulings/.render-run.mjs writes the whole run as one page, the ruling first and every call down a clock, and the runner opens it. A terminal is a hard place to read five tool calls.Nothing in the runner computes a number. It moves the claim, the policy, and the tool's printout between the pieces, and writes down what happened.
See it for yourself
# the example on this page node ./query-plays.mjs --seasons 2021-2025 --half 1 # every filter it accepts node ./query-plays.mjs --help # a filter it does not know: it stops and says so node ./query-plays.mjs --seasons 2026 --spread 3