.aspect/changed.axl, 49 lines. Read all of it before you run it — the diff is the lesson in every section of this course. About a dozen of those lines are the module docstring and inline comments; the rest is two functions and the argument declarations that become --help.
What a task is
A task is a Starlark function plus a declaration of what it’s called and what arguments it takes:changed = task(...) is aspect changed. Snake case becomes kebab case, so my_build would be aspect my-build; kind = "..." overrides it if you need something else.
task() has no name argument. The CLI command name is the task’s kind, derived from the variable at export. The task name is a separate, per-invocation thing set with --task:name — you’ll meet it only if you wire tasks into CI..aspect/, so the CLI found it. Nothing lists commands, nothing needs regenerating.
Run it
git calls produce that — merge-base, diff --name-only, ls-files --others — and they cover all four states: committed, staged, unstaged and untracked. Only two of the three do any listing, because git diff <merge-base> already spans the index, so staged and unstaged edits arrive together. The naive version of this needs a command per state.
Now pipe it
This is the important step. Don’t skip it.ctx.std.io.stdout.write(...).
print() in AXL writes to stderr. Had the task used print(), everything above would have looked identical — the paths would have appeared on your terminal, and aspect changed | wc -l would have printed 0. The bug is invisible until you pipe, and no tool can catch it for you.
The flip side is a free win: because the 🎬 and ✅ banner lines are also on stderr, the JSON format needs no flags to be machine-readable.
Two kinds of failure
values = [...] in the declaration is enforced by the CLI’s argument parser before any Starlark executes. Compare that with a task that runs and then fails, which exits 1. Two failure classes, two exit codes, one line of declaration.
It is already discoverable
--help and enforces --format also published this. Remember it — the last section of the course is about who reads it.
Your turn
Change the default--base from main to HEAD~2, and run aspect changed again:
.aspect/changed.axl itself.
HEAD~2 and not HEAD~1, because on step-2 HEAD~1 is step-1. Try it and you get .aspect/changed.axl, the file you just edited, plus whatever else is still dirty from the section above — which reads like “my change did nothing” and is in fact the correct answer to a different question. The off-by-one is in the base you picked, not in the task.

