command-development · git:20261024.772383e · 2026-10-24 · sha256 ad337c55fd23edc5
command-development git:20261024.772383eA
Immutable. This exact content never changes and is served at /api/v1/blob/ad337c55fd23edc5.
---
name: command-development
description: Write Navin slash commands as Markdown prompt files (frontmatter, $ARGUMENTS, $1..$9, ${1:-default}, @file inlining, !`cmd` shell output, allowed-tools, namespaces, plugin commands with ${NAVIN_PLUGIN_ROOT}). Use when the user asks to "create a slash command", "add a custom command", "write a command that takes arguments", "inline a file in a command", "run a shell command inside a command", "restrict the tools of a command", "add a command to my plugin", or asks how Markdown commands are discovered and expanded.
---
# Command Development
A slash command is a Markdown file whose body is a prompt. When the user types
`/<name> <arguments>`, Navin expands the file (arguments, referenced files,
shell output) and runs the result as one agent turn. Commands package a
repeatable workflow behind a short name: a review checklist, a release routine,
a scaffold.
## Commands are instructions for Navin
The body is written **for Navin**, not for the user. It gives instructions
("Review the staged diff and report..."), never a message ("This command will
review your code"). The user sees the result of the turn, not the prompt.
Correct:
```markdown
Review the staged changes for security issues: injection, unsafe file access,
secrets in code. Report each finding with file, line and severity.
```
Wrong:
```markdown
This command reviews your code for security issues and you will get a report.
```
## Where commands live
| Scope | Folder | Notes |
|---|---|---|
| Project | `<workspace>/.navin/commands/` | shared with the repository |
| Personal | `~/.navin/commands/` | every workspace of this user |
| Plugin | `<plugin>/commands/` (or the `commands` paths of `plugin.json`) | while the plugin is enabled |
| Shipped | inside Navin (`/create-plugin`) | always available |
Rules:
- The command name is the file stem in lower case: `review-pr.md` gives `/review-pr`.
The stem must start with a letter or digit (letters, digits, `_`, `.`, `-`).
- Built-in commands of Navin (`/help`, `/new`, `/goal`...) always win; a file
named after one of them is never reached.
- On a name clash: project, then personal, then plugin, then shipped.
- Plugin commands are also reachable as `/<plugin>:<name>`, which avoids clashes.
- Subfolders are namespaces for humans: `.navin/commands/git/sync.md` is still
`/sync`. Mention the namespace in the description.
- `README.md` files are ignored, so are files over 128 KB and files with an
empty body.
- Commands are read when invoked, so an edit takes effect on the next call.
Create a project command:
```bash
mkdir -p .navin/commands
cat > .navin/commands/review.md <<'EOF'
Review the staged diff for bugs, missing tests and unclear names.
EOF
```
## File format
The smallest command is plain Markdown with no frontmatter; the whole file is
the prompt:
```markdown
Run the test suite, then summarize failures grouped by module with the most
likely cause for each.
```
With frontmatter:
```markdown
---
description: Review a pull request branch against the project checklist
argument-hint: <branch> [focus]
allowed-tools: Read, Grep, Bash(git:*)
---
Review the branch $1 against main. Focus on ${2:-correctness and tests}.
Changed files:
!`git diff --stat main...$1`
Apply the checklist in @docs/review-checklist.md and report findings as a
table: file, line, severity, fix.
```
## Frontmatter fields
| Field | Effect in Navin |
|---|---|
| `description` | shown in the command list and in the system prompt index; keep it under 80 characters |
| `argument-hint` | shown next to the name, e.g. `<file> [mode]` |
| `allowed-tools` | restricts the tools of the turn (see below) |
| `model` | accepted for compatibility; the turn runs on the session model |
| `disable-model-invocation` | accepted for compatibility; Markdown commands only run when typed |
### description
```yaml
---
description: Generate release notes from commits since the last tag
---
```
Start with a verb, say what the command produces. Without it the list shows
only the name.
### allowed-tools
```yaml
---
allowed-tools: Read, Grep, Bash(git:*)
---
```
or as a YAML list:
```yaml
---
allowed-tools:
- read_file
- grep
- exec
---
```
Common names from other harnesses are mapped to Navin tools (`Bash` to `exec`,
`Read` to `read_file`, `Write` to `write_file`, `Edit` to `edit_file` and
`apply_patch`, `Grep`, `Glob` to `find_files`, `WebFetch` to `web_fetch`,
`Task` to `spawn`). Navin tool names work directly. Argument filters such as
`Bash(git:*)` are accepted; the filter is documentation, the whole tool is
granted. `skill` and `read_file` are always kept so the command can load
playbooks and read its own references. Omit the field to inherit every tool of
the session.
### model
```yaml
---
model: haiku
---
```
Navin parses the key but ignores it: the command runs on the model of the
current session. Choose the model per session or per subagent
(`spawn model_preset=...`) instead.
### argument-hint
```yaml
---
argument-hint: <environment> [version] [--dry-run]
---
```
Use `<required>` and `[optional]`, in the same order as `$1`, `$2`... The hint
appears in the command list so the user knows what to type.
### disable-model-invocation
```yaml
---
disable-model-invocation: true
---
```
Accepted and ignored. Navin never invokes Markdown commands on its own: they
run only when the user types them, so every command already behaves as if this
flag were set.
See `references/frontmatter-reference.md` for YAML pitfalls and the full
tool-name mapping.
## Dynamic arguments
| Placeholder | Replaced by |
|---|---|
| `$ARGUMENTS` | everything after the command name, verbatim |
| `$1` .. `$9` | positional arguments, split like a shell (quotes group words) |
| `${1:-text}` | positional argument, or `text` when it is missing |
A missing positional becomes an empty string. When the body uses no
placeholder at all, Navin appends the arguments as a final `Arguments: ...`
line, so a command never silently drops what the user typed.
### $ARGUMENTS
```markdown
---
description: Explain a piece of the codebase
argument-hint: <topic>
---
Explain how $ARGUMENTS works in this repository. Cite files and functions.
```
`/explain the retry logic of the gateway` gives "Explain how the retry logic
of the gateway works...".
### Positional arguments
```markdown
---
description: Compare two git refs
argument-hint: <base> <head>
---
Compare $1 with $2. List changed modules, risky edits and missing tests.
```
`/compare main "feature/login form"` sets `$1=main` and
`$2=feature/login form` (quotes group words, as in a shell).
### Defaults and combinations
```markdown
---
argument-hint: <file> [level]
---
Review @$1 at strictness ${2:-normal}. Extra notes from the user: $ARGUMENTS
```
`${N:-default}` is resolved first, then `$ARGUMENTS`, then `$1`..`$9`. Only
digits 1 to 9 are positional; `$10` is not.
## File references
`@path` inlines a file before the turn starts, wrapped in a `<file path="...">`
block. Paths are relative to the workspace; plugin commands may use
`@${NAVIN_PLUGIN_ROOT}/templates/report.md`. Arguments work too: `@$1`.
```markdown
---
argument-hint: <file>
---
Review @$1 for readability and error handling. Suggest concrete edits.
```
Several files at once:
```markdown
Compare the old and new configuration:
@config/settings.old.yaml
@config/settings.yaml
List every changed key and its impact.
```
Static references, always the same file:
```markdown
Check the code against our rules in @docs/coding-standards.md and
@docs/security-checklist.md.
```
Only files inside the workspace or the plugin folder are inlined, up to 20 000
characters each. A reference that cannot be inlined (missing file, outside
those roots) stays as literal text, so the turn can still read the file with
`read_file`. Email addresses and code spans are not treated as references.
## Inline shell output
`` !`command` `` runs a shell command before the turn and inlines its output in a
fenced block. Use it to hand fresh context to the prompt (`git status`, test
lists, versions).
```markdown
---
description: Write a commit message for the staged changes
allowed-tools: Bash(git:*)
---
Staged files:
!`git diff --cached --stat`
Diff:
!`git diff --cached`
Write a conventional commit message for these changes. Do not commit.
```
- It runs **only** when `allowed-tools` grants the shell (`Bash`, `Bash(...)` or
`exec`). Otherwise the snippet stays as text and nothing executes.
- Working directory: the workspace. Environment: `NAVIN_PROJECT_DIR`, and
`NAVIN_PLUGIN_ROOT` for plugin commands.
- Timeout 30 seconds; output capped at 20 000 characters. On a non-zero exit
stderr is appended, so the turn sees the error.
- The snippet must fit on one line and contain no backtick.
- Keep these commands read-only and fast. Anything that changes state belongs
in the instructions, where the user sees the tool call.
## Organizing commands
Flat, for a handful of commands:
```
.navin/commands/
├── review.md
├── test.md
└── release.md
```
Namespaced, for many commands:
```
.navin/commands/
├── git/
│ ├── sync.md -> /sync
│ └── changelog.md -> /changelog
└── docs/
└── api.md -> /api
```
The folder does not prefix the name, so keep stems unique across folders and
mention the group in the description ("git: rebase the branch on main").
## Best practices
- One job per command; a clear verb in the name (`/review-pr`, `/bump-version`).
- Always set `description` and, when arguments are expected, `argument-hint`.
- Grant the fewest tools; add the shell only for `!` snippets you need.
- Handle the empty-argument case and give defaults with `${N:-...}`.
- Prefer `@file` for stable context and `!` for live state.
- Keep the body under 150 lines; move long checklists into a referenced file.
- Document non-obvious commands with an HTML comment at the top; it stays in
the prompt but is harmless:
```markdown
---
description: Deploy to an environment
argument-hint: <staging|production> [version]
---
<!--
Usage: /deploy staging 1.4.2
Requires: a clean working tree, gh authenticated.
-->
Deploy version ${2:-latest} to $1 following docs/deploy.md.
```
## Common patterns
### Review
```markdown
---
description: Review changed files
allowed-tools: Read, Grep, Bash(git:*)
---
Changed files:
!`git diff --name-only HEAD`
Review each file for bugs, security issues and missing tests. Report a table
with file, line, severity and fix.
```
### Testing
```markdown
---
description: Run tests for one file
argument-hint: <test-file>
allowed-tools: Bash(pytest:*)
---
Test output:
!`python -m pytest $1 -q`
Explain each failure and propose the smallest fix.
```
### Documentation
```markdown
---
description: Document a module
argument-hint: <source-file>
---
Write reference documentation for @$1: purpose, public functions with
parameters and return values, one usage example each.
```
### Workflow
```markdown
---
description: Prepare a release
argument-hint: <version>
allowed-tools: Bash(git:*), Read, Edit
---
Current tag: !`git describe --tags --abbrev=0`
1. If $1 is empty, ask the user for the version with ask_user and stop.
2. Update the version in pyproject.toml to $1.
3. Add a CHANGELOG entry from the commits since the current tag.
4. Show the diff and wait for confirmation before tagging.
```
## Interactive commands
When a choice cannot be guessed, tell Navin to ask with `ask_user` instead of
assuming:
```markdown
---
description: Configure the review strictness of this project
---
Ask the user with ask_user: "Which strictness level?" with the options
lenient, normal, strict. Then write the answer to
.navin/review.local.md as `strict: <answer>` and confirm.
```
Ask few, closed questions, and only when the answer changes what happens. More
patterns: `references/interactive-commands.md`.
## Commands in plugins
Put files in `<plugin>/commands/` (next to `plugin.json` at the pack root).
Use `${NAVIN_PLUGIN_ROOT}` for every path inside the plugin: Navin replaces it
in the body, in `@` references and in the environment of inline shell
commands. Never hard-code `~/.navin/plugins/...`.
```markdown
---
description: Analyze the project with the plugin script
allowed-tools: Bash(python:*), Read
---
Analysis:
!`python "${NAVIN_PLUGIN_ROOT}/scripts/analyze.py" .`
Report template: @${NAVIN_PLUGIN_ROOT}/templates/report.md
Fill the template with the analysis above.
```
Typical uses of the plugin root:
```markdown
Run the plugin script: !`bash ${NAVIN_PLUGIN_ROOT}/scripts/check.sh`
Load plugin configuration: @${NAVIN_PLUGIN_ROOT}/config/defaults.json
Use the plugin template: @${NAVIN_PLUGIN_ROOT}/templates/pr.md
Read a plugin resource on demand: ${NAVIN_PLUGIN_ROOT}/docs/rules.md
```
Plugin layout:
```
my-plugin/
├── plugin.json
├── commands/
│ ├── analyze.md -> /analyze or /my-plugin:analyze
│ └── report.md
├── scripts/
│ └── analyze.py
└── templates/
└── report.md
```
Users who have another `/analyze` can always type `/my-plugin:analyze`.
### Script and template patterns
```markdown
---
description: Validate the configuration with the plugin validator
argument-hint: <config-file>
allowed-tools: Bash(bash:*)
---
!`bash "${NAVIN_PLUGIN_ROOT}/scripts/validate.sh" "$1"`
Explain each reported error and how to fix it.
```
```markdown
---
description: Generate a report from the plugin template
argument-hint: <topic>
---
Using the structure of @${NAVIN_PLUGIN_ROOT}/templates/report.md, write a
report about $ARGUMENTS and save it to reports/$1.md.
```
Details: `references/plugin-features-reference.md`. Before publishing, read
`references/marketplace-considerations.md`.
## Integration with other components
### Agents
```markdown
---
description: Deep review with the plugin reviewer agent
argument-hint: <path>
---
Use spawn with agent=code-reviewer to review $1. Ask it for findings with file,
line and severity, then summarize them for the user.
```
### Skills
```markdown
---
description: Document the public API
---
Load the technical-writer skill with the skill tool and follow it to document
the public API of this package.
```
### Hooks
Commands and hooks do not call each other directly. Coordinate through a file:
the command writes `.navin/<plugin>.local.md`, a `command` hook (the only hook
type in Navin) reads it on its next event. See the plugin-settings skill.
### Multi-component workflow
```markdown
---
description: Full quality pass
allowed-tools: Bash(bash:*), Read
---
Lint results: !`bash "${NAVIN_PLUGIN_ROOT}/scripts/lint.sh"`
1. Spawn agent=test-writer for uncovered modules.
2. Load the code-reviewer skill and review the result.
3. Write a summary to .navin/quality-report.md.
```
More patterns: `references/advanced-workflows.md`.
## Validation patterns
### Argument validation
```markdown
---
argument-hint: <staging|production>
---
If "$1" is not exactly staging or production, reply with the usage
"/deploy <staging|production>" and stop. Otherwise deploy to $1.
```
### File existence
```markdown
---
argument-hint: <file>
allowed-tools: Bash(test:*)
---
Check: !`test -f "$1" && echo found || echo missing`
If the check says missing, tell the user the file $1 does not exist and stop.
Otherwise review it.
```
### Plugin resource validation
```markdown
---
allowed-tools: Bash(test:*)
---
!`test -x "${NAVIN_PLUGIN_ROOT}/scripts/run.sh" && echo ok || echo "run.sh missing"`
If the line above is not ok, report that the plugin install is broken and stop.
```
### Error handling
```markdown
---
allowed-tools: Bash(make:*)
---
Build output:
!`make build 2>&1 || echo "BUILD FAILED"`
If the output contains BUILD FAILED, explain the first error and suggest a fix.
Do not continue to the deploy step.
```
## Troubleshooting
| Symptom | Cause and fix |
|---|---|
| `/name` does nothing special | a built-in command has the same name; rename the file |
| Wrong file runs | a project or personal command shadows it; use `/<plugin>:<name>` |
| `!` snippet shown as text | `allowed-tools` does not grant `Bash`/`exec` |
| `@file` not inlined | path outside workspace or plugin root, or file missing |
| Arguments ignored | no placeholder used; they arrive as an `Arguments:` line |
| Command not listed | empty body, file over 128 KB, or named README.md |
## Testing
1. Create the file, then run `/<name>` with no argument, one argument, and a
quoted multi-word argument.
2. Check the expanded prompt at the top of the turn (`[Command /name ...]`).
3. Confirm `!` snippets ran only with the shell granted.
4. Validate a plugin with the `plugin-validator` agent, or with the validator
script shipped in the `plugin-structure` skill.
The full checklist is in `references/testing-strategies.md`.
## Additional resources
- `references/frontmatter-reference.md` - every field, YAML rules, tool mapping
- `references/advanced-workflows.md` - multi-step, state, agents, skills
- `references/interactive-commands.md` - asking the user with ask_user
- `references/documentation-patterns.md` - usage blocks, help output
- `references/plugin-features-reference.md` - plugin root, namespacing, components
- `references/marketplace-considerations.md` - portability and distribution
- `references/testing-strategies.md` - test matrix and debugging
- `examples/simple-commands.md` - complete standalone commands
- `examples/plugin-commands.md` - commands that use plugin files and scripts