Skip to main content
One new file, .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:
The variable name became the command. 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.
There is no registration step. The file is under .aspect/, so the CLI found it. Nothing lists commands, nothing needs regenerating.

Run it

Nothing, because the tree is clean. Change something:
Three 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.
One line, as expected. That only works because the task writes with 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.
Which is the rule worth taking home: stdout is the contract, stderr is the conversation.

Two kinds of failure

Exit code 2, and your implementation never ran. The 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

You wrote no manifest and no documentation. The same declaration that generates --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:
The 255 files that landed in step-1, plus .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.
Next: turn those filenames into something you can act on.