> ## Documentation Index
> Fetch the complete documentation index at: https://aspect.build/llms.txt
> Use this file to discover all available pages before exploring further.

# Your first command

> Read a fifty-line AXL task, run it, and learn which output stream a command's contract lives on.

```shell theme={null}
git checkout step-2
git diff step-1 step-2
```

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:

```python theme={null}
changed = task(
    summary = "List the files changed in the working tree, relative to a base branch.",
    implementation = _impl,
    args = {
        "base": args.string(default = "main", description = "..."),
        "format": args.string(default = "human", values = ["human", "json"], description = "..."),
    },
)
```

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

<Note>
  `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.
</Note>

There is no registration step. The file is under `.aspect/`, so the CLI found it. Nothing lists commands, nothing needs regenerating.

## Run it

```shell theme={null}
aspect changed
```

```text theme={null}
→ 🎬 Running changed task

→ ✅ Passed changed task in 82ms
```

Nothing, because the tree is clean. Change something:

```shell theme={null}
printf '\n// a change\n' >> lib/log/log.go
aspect changed
```

```text theme={null}
→ 🎬 Running changed task
lib/log/log.go

→ ✅ Passed changed task in 83ms
```

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.

```shell theme={null}
aspect changed | wc -l
```

```text theme={null}
       1
```

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.

```shell theme={null}
aspect changed --format=json | jq -e . >/dev/null && echo "valid json"
```

```text theme={null}
valid json
```

Which is the rule worth taking home: **stdout is the contract, stderr is the conversation.**

## Two kinds of failure

```shell theme={null}
aspect changed --format=bogus
```

```text theme={null}
error: invalid value 'bogus' for '--format <format>'
  [possible values: human, json]

For more information, try '--help'.
```

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

```shell theme={null}
aspect describe changed | jq '{command, defined_in, args: [.args[].name]}'
```

```json theme={null}
{
  "command": "aspect changed",
  "defined_in": ".aspect/changed.axl",
  "args": [
    "base",
    "format"
  ]
}
```

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:

```shell theme={null}
aspect changed | wc -l
```

```text theme={null}
     256
```

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.

```shell theme={null}
git checkout -- .
```

Next: turn those filenames into something you can act on.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.