Skip to content

Commit a20fc18

Browse files
authored
Add an AGENTS.md file (#15)
1 parent dfcf391 commit a20fc18

17 files changed

Lines changed: 902 additions & 422 deletions

.github/workflows/tests.yml

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,38 @@ permissions:
2121

2222
jobs:
2323
tests:
24-
runs-on: ubuntu-latest
24+
runs-on: ${{ matrix.os }}
25+
strategy:
26+
fail-fast: false
27+
matrix:
28+
include:
29+
- os: ubuntu-latest
30+
zig-url: https://ziglang.org/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz
31+
zig-dir: zig-x86_64-linux-0.16.0
32+
- os: macos-latest
33+
zig-url: https://ziglang.org/download/0.16.0/zig-aarch64-macos-0.16.0.tar.xz
34+
zig-dir: zig-aarch64-macos-0.16.0
35+
- os: windows-latest
36+
zig-url: https://ziglang.org/download/0.16.0/zig-x86_64-windows-0.16.0.zip
37+
zig-dir: zig-x86_64-windows-0.16.0
2538

2639
steps:
2740
- name: Checkout repository
2841
uses: actions/checkout@v4
2942

30-
- name: Install Zig 0.16.0
43+
- name: Install Zig 0.16.0 (Unix)
44+
if: runner.os != 'Windows'
3145
run: |
32-
curl -sSfL https://ziglang.org/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz | tar -xJ
33-
echo "$PWD/zig-x86_64-linux-0.16.0" >> "$GITHUB_PATH"
46+
curl -sSfL ${{ matrix.zig-url }} | tar -xJ
47+
echo "$PWD/${{ matrix.zig-dir }}" >> "$GITHUB_PATH"
3448
35-
- name: Install dependencies
49+
- name: Install Zig 0.16.0 (Windows)
50+
if: runner.os == 'Windows'
51+
shell: pwsh
3652
run: |
37-
sudo apt-get update
38-
sudo apt-get install -y make
53+
Invoke-WebRequest -Uri "${{ matrix.zig-url }}" -OutFile zig.zip
54+
Expand-Archive zig.zip -DestinationPath .
55+
echo "$PWD\${{ matrix.zig-dir }}" | Out-File -Append -FilePath $env:GITHUB_PATH
3956
40-
- name: Run the tests
41-
run: make test
57+
- name: Run tests
58+
run: zig build test --summary all

AGENTS.md

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# AGENTS.md
2+
3+
This file provides guidance to coding agents collaborating on this repository.
4+
5+
## Mission
6+
7+
Chilli is a command-line interface (CLI) microframework for Zig.
8+
It turns a declarative description of commands, flags, and positional arguments into a parser, help generator, and dispatcher, with zero external
9+
dependencies.
10+
Priorities, in order:
11+
12+
1. Correctness of argument parsing, flag resolution, and help output.
13+
2. Minimal public API for defining and running command trees from other Zig projects.
14+
3. Zero non-Zig dependencies, maintainable, and well-tested code.
15+
4. Cross-platform support (Linux, macOS, and Windows).
16+
17+
## Core Rules
18+
19+
- Use English for code, comments, docs, and tests.
20+
- Prefer small, focused changes over large refactoring.
21+
- Add comments only when they clarify non-obvious behavior.
22+
- Do not add features, error handling, or abstractions beyond what is needed for the current task.
23+
- Keep the project dependency-free: no external Zig packages or C libraries unless explicitly agreed.
24+
25+
## Writing Style
26+
27+
- Use Oxford commas in inline lists: "a, b, and c" not "a, b, c".
28+
- Do not use em dashes. Restructure the sentence, or use a colon or semicolon instead.
29+
- Avoid colorful adjectives and adverbs. Write "TCP proxy" not "lightweight TCP proxy", "scoring components" not "transparent scoring components".
30+
- Use noun phrases for checklist items, not imperative verbs. Write "redundant index detection" not "detect redundant indexes".
31+
- Headings in Markdown files must be in the title case: "Build from Source" not "Build from source". Minor words (a, an, the, and, but, or, for, in,
32+
on, at, to, by, of, is, are, was, were, be) stay lowercase unless they are the first word.
33+
34+
## Repository Layout
35+
36+
- `src/lib.zig`: Public API entry point. Re-exports `Command`, `CommandOptions`, `Flag`, `FlagType`, `FlagValue`, `PositionalArg`, `CommandContext`,
37+
`styles`, and `Error`.
38+
- `src/chilli/command.zig`: The `Command` struct (command tree, init/deinit, `run`, subcommand and flag registration).
39+
- `src/chilli/types.zig`: Core types (`CommandOptions`, `Flag`, `FlagType`, `FlagValue`, `PositionalArg`) and the `parseValue` helper.
40+
- `src/chilli/parser.zig`: Argument-string parser (`ArgIterator`, `ParsedFlag`, long/short/grouped flag handling, positional handling).
41+
- `src/chilli/context.zig`: The `CommandContext` passed to each command's `exec` function for typed flag and argument access.
42+
- `src/chilli/errors.zig`: Error types produced by parsing and type coercion.
43+
- `src/chilli/utils.zig`: Shared helpers (`styles` for ANSI colors, `parseBool`, and other small utilities).
44+
- `examples/`: Self-contained example programs (`e1_simple_cli.zig` through `e8_flags_and_args.zig`) built as executables via `build.zig`.
45+
- `.github/workflows/`: CI workflows (`tests.yml` for unit tests on Linux and Windows, `docs.yml` for API doc deployment).
46+
- `build.zig` / `build.zig.zon`: Zig build configuration and package metadata.
47+
- `Makefile`: GNU Make wrapper around `zig build` targets.
48+
- `docs/`: Generated API docs land in `docs/api/` (produced by `make docs`).
49+
50+
## Architecture
51+
52+
### Command Tree
53+
54+
A Chilli application is a tree of `Command` nodes. Each node owns its `flags`, `positional_args`, optional `exec`
55+
function, and a list of subcommands. `Command.init` allocates a node; `Command.deinit` recursively frees the subtree, so
56+
downstream users call `deinit` only on the root.
57+
58+
### Parsing Pipeline
59+
60+
Arguments flow through: `ArgIterator` over `[][]const u8` (`parser.zig`) -> per-node flag and positional resolution
61+
(`parser.zig` + `command.zig`) -> `CommandContext` population (`context.zig`) -> `exec` dispatch on the resolved leaf
62+
command.
63+
64+
### Flag and Positional Types
65+
66+
`FlagType` (`types.zig`) enumerates the supported value kinds (`Bool`, `Int`, `Float`, `String`). `FlagValue` is the matching-tagged union.
67+
`types.parseValue` is the single conversion point from raw strings into a `FlagValue`; every new type or coercion belongs here.
68+
69+
### Help and Version Output
70+
71+
Help and version text are generated automatically from the command tree at runtime by `command.zig`, using the metadata in `CommandOptions` (name,
72+
description, version, sections) and the registered flags and positional args. Grouping into
73+
named sections is supported; custom help formatting beyond that should be added sparingly.
74+
75+
### Public API Surface
76+
77+
Everything re-exported from `src/lib.zig` is part of the public API. Changes to names or signatures there are breaking.
78+
The rest of `src/chilli/` is internal and may be refactored freely as long as the public surface and its behavior are
79+
preserved.
80+
81+
### Dependencies
82+
83+
Chilli has **no external Zig or C dependencies**.
84+
The only `build.zig.zon` entries should be Chilli itself.
85+
Please do not add dependencies without prior discussion.
86+
87+
## Zig Conventions
88+
89+
- Zig version: 0.16.0 (as declared in `build.zig.zon` and the Makefile's `ZIG_LOCAL` path).
90+
- Formatting is enforced by `zig fmt`. Run `make format` before committing.
91+
- Naming follows Zig standard-library conventions: `camelCase` for functions (e.g. `addFlag`, `getFlag`, `parseBool`), `snake_case` for local
92+
variables and struct fields, `PascalCase` for types and structs, and `SCREAMING_SNAKE_CASE` for top-level compile-time constants.
93+
94+
## Required Validation
95+
96+
Run the relevant targets for any change:
97+
98+
| Target | Command | What It Runs |
99+
|----------------|----------------------------------|------------------------------------------------------------------|
100+
| Unit tests | `make test` | Inline `test` blocks across `src/lib.zig` and `src/chilli/*.zig` |
101+
| Lint | `make lint` | Checks Zig formatting with `zig fmt --check src examples` |
102+
| Single example | `make run EXAMPLE=e1_simple_cli` | Builds and runs one example program |
103+
| All examples | `make run` | Builds and runs every example under `examples/` |
104+
| Docs | `make docs` | Generates API docs into `docs/api` |
105+
| Everything | `make all` | Runs `build`, `test`, `lint`, and `docs` |
106+
107+
## First Contribution Flow
108+
109+
1. Read the relevant module under `src/chilli/` (often `command.zig`, `parser.zig`, or `types.zig`).
110+
2. Implement the smallest change that covers the requirement.
111+
3. Add or update inline `test` blocks in the changed Zig module to cover the new behavior.
112+
4. Run `make test` and `make lint`.
113+
5. If parser or help-output behavior changed, also exercise the examples with `make run` and confirm the `--help` output is still correct.
114+
115+
Good first tasks:
116+
117+
- New example under `examples/` that demonstrates an API pattern, listed in `examples/README.md`.
118+
- New flag-coercion case in `src/chilli/types.zig` `parseValue` (with an inline `test` block).
119+
- Error message refinement in `src/chilli/errors.zig`, paired with a test that asserts the exact message.
120+
- Help-formatting refinement in `src/chilli/command.zig`.
121+
122+
## Testing Expectations
123+
124+
- Unit and regression tests live as inline `test` blocks in the module they cover (`src/lib.zig` and `src/chilli/*.zig`). There is no separate
125+
`tests/` directory.
126+
- Tests are discovered automatically via `std.testing.refAllDecls(@This())` in `src/lib.zig`, so new `test` blocks only need to live in a module that
127+
is reachable from `lib.zig`.
128+
- Every new public function, flag type, or parser branch must ship with at least one `test` block that exercises it, including the error paths where
129+
applicable.
130+
- Tests that touch argument parsing should build their input as `[][]const u8` explicitly, not read from `std.process.args()`, so they work under
131+
`zig test` without a real process-args environment.
132+
- No public API change is complete without a test covering the new or changed behavior.
133+
134+
## Change Design Checklist
135+
136+
Before coding:
137+
138+
1. Modules affected by the change (`command`, `parser`, `types`, `context`, `errors`, or `utils`).
139+
2. Whether the change is user-visible in `--help` output, and if so, which examples will surface it.
140+
3. Public API impact, i.e. whether the change adds to or alters anything re-exported from `src/lib.zig`, and is therefore additive or breaking.
141+
4. Cross-platform implications, especially for anything that touches environment variables, the filesystem, or process-args encoding.
142+
143+
Before submitting:
144+
145+
1. `make test` passes.
146+
2. `make lint` passes.
147+
3. `make run` succeeds for all examples when touching the parser, command dispatch, or help-output code.
148+
4. Docs updated (`make docs`) if the public API surface changed, and `ROADMAP.md` ticked or updated if a listed item was implemented.
149+
150+
## Commit and PR Hygiene
151+
152+
- Keep commits scoped to one logical change.
153+
- PR descriptions should include:
154+
1. Behavioral change summary.
155+
2. Tests added or updated.
156+
3. Whether examples were run locally (yes/no), and on which OS.

CONTRIBUTING.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ would like to work on or if it has already been resolved.
2828
2929
### Development Workflow
3030

31+
> [!IMPORTANT]
32+
> If you're using an AI-assisted coding tool like Claude Code or Codex, make sure the AI follows the instructions in the [AGENTS.md](AGENTS.md) file.
33+
3134
#### Prerequisites
3235

3336
Install GNU Make on your system if it's not already installed.

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ docs: ## Generate API documentation
8181
@echo "Generating API documentation..."
8282
@$(ZIG) build docs
8383

84-
serve-docs: ## Serve the generated documentation on a local server
84+
serve-docs: docs ## Serve the generated documentation on a local server
8585
@echo "Serving documentation at http://localhost:8000..."
8686
@cd docs/api && python3 -m http.server 8000
8787

README.md

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ A microframework for creating command-line applications in Zig
1919

2020
---
2121

22-
Chilli is a lightweight command-line interface (CLI) framework for the Zig programming language.
23-
Its goal is to make it easy to create structured, maintainable, and user-friendly CLIs with minimal boilerplate,
24-
while being small and fast, and not getting in the way of your application logic.
22+
Chilli is a command-line interface (CLI) framework for Zig.
23+
It turns a declarative description of commands, flags, and positional arguments into a parser, help generator, and
24+
dispatcher, with zero external dependencies and minimal boilerplate.
2525

2626
### Features
2727

28-
- Provides a simple, low-overhead, declarative API for building CLI applications
28+
- Provides a declarative API for building CLI applications
2929
- Supports nested commands, subcommands, and aliases
3030
- Provides type-safe parsing for flags, positional arguments, and environment variables
3131
- Supports generating automatic `--help` and `--version` output with custom sections
@@ -52,10 +52,19 @@ Run the following command in the root directory of your project to download Chil
5252
zig fetch --save=chilli "https://github.com/CogitatorTech/chilli/archive/<branch_or_tag>.tar.gz"
5353
```
5454

55-
Replace `<branch_or_tag>` with the desired branch or tag, like `main` (for the development version) or `v0.2.3`
55+
Replace `<branch_or_tag>` with the desired branch or tag, like `main` (for the development version) or `v0.3.0`
5656
(for the specified release version).
5757
This command will download Chilli and add it to Zig's global cache and update your project's `build.zig.zon` file.
5858

59+
Zig version supported by the main releases of Chilli:
60+
61+
| Zig | Chilli Tags |
62+
|----------|-------------|
63+
| `0.16.0` | `v0.3.x` |
64+
| `0.15.x` | `v0.2.x` |
65+
66+
The `main` branch normally tracks the latest (non-developmental) Zig release.
67+
5968
#### Adding to Build Script
6069

6170
Next, modify your `build.zig` file to make Chilli available to your build target as a module.
@@ -150,17 +159,17 @@ You can now run your CLI application with the `--help` flag to see the output be
150159

151160
```bash
152161
$ ./your-cli-app --help
153-
your-cli-app v0.3.0
154162
A new CLI built with Chilli
163+
Version: v0.1.0
155164

156-
USAGE:
157-
your-cli-app [FLAGS]
165+
Usage:
166+
your-cli-app [flags]
158167

159-
FLAGS:
160-
-n, --name <string> The name to greet [default: World]
161-
--excitement <int> How excited to be [default: 1]
162-
-h, --help Prints help information
163-
-V, --version Prints version information
168+
Flags:
169+
-h, --help Shows help information for this command [Bool] (default: false)
170+
--version Print version information and exit [Bool] (default: false)
171+
-n, --name The name to greet [String] (default: "World")
172+
--excitement How excited to be [Int] (default: 1)
164173
```
165174

166175
---

ROADMAP.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,24 @@ It outlines features to be implemented and their current status.
66
> [!IMPORTANT]
77
> This roadmap is a work in progress and is subject to change.
88
9-
- **Command Structure**
9+
- **Command Structure**
1010
- [x] Nested commands and subcommands
1111
- [x] Command aliases and single-character shortcuts
1212
- [x] Persistent flags (flags on parent commands are available to children)
1313

14-
- **Argument & Flag Parsing**
14+
- **Argument & Flag Parsing**
1515
- [x] Long flags (`--verbose`), short flags (`-v`), and grouped boolean flags (`-vf`)
1616
- [x] Positional Arguments (supports required, optional, and variadic)
1717
- [x] Type-safe access for flags and arguments (e.g., `ctx.getFlag("count", i64)`)
1818
- [x] Reading flag values from environment variables
1919

20-
- **Help & Usage Output**
20+
- **Help & Usage Output**
2121
- [x] Automatic and context-aware `--help` flag
2222
- [x] Automatic `--version` flag
2323
- [x] Clean, aligned help output for commands, flags, and arguments
2424
- [x] Grouping subcommands into custom sections
2525

26-
- **Developer Experience**
26+
- **Developer Experience**
2727
- [x] Simple, declarative API for building commands
2828
- [x] Named access for all flags and arguments
2929
- [x] Shared context data for passing application state

build.zig

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,18 @@ pub fn build(b: *std.Build) void {
3131
const docs_step = b.step("docs", "Generate API documentation");
3232
const doc_install_path = "docs/api";
3333

34+
// Zig's `-femit-docs=<path>` writes the leaf dir but does not create
35+
// intermediate parents, and git does not track empty directories, so a
36+
// fresh checkout may have no `docs/` at all. Create it portably here
37+
// (idempotent: createDirPath is a no-op when the directory already exists).
38+
const ensure_docs_dir = EnsureDirStep.create(b, "docs");
3439
const gen_docs_cmd = b.addSystemCommand(&[_][]const u8{
3540
b.graph.zig_exe, // Use the same zig that is running the build
3641
"build-lib",
3742
"src/lib.zig",
3843
"-femit-docs=" ++ doc_install_path,
3944
});
40-
41-
const mkdir_cmd = b.addSystemCommand(&[_][]const u8{
42-
"mkdir", "-p", doc_install_path,
43-
});
44-
gen_docs_cmd.step.dependOn(&mkdir_cmd.step);
45+
gen_docs_cmd.step.dependOn(&ensure_docs_dir.step);
4546

4647
docs_step.dependOn(&gen_docs_cmd.step);
4748

@@ -100,3 +101,32 @@ pub fn build(b: *std.Build) void {
100101
}
101102
}
102103
}
104+
105+
/// Build step that ensures a directory (relative to the build root) exists.
106+
/// Runs `std.fs.Dir.createDirPath` at make-time, so it only fires when a
107+
/// step that depends on it is actually being built. Portable across Linux,
108+
/// macOS, and Windows.
109+
const EnsureDirStep = struct {
110+
step: std.Build.Step,
111+
sub_path: []const u8,
112+
113+
fn create(b: *std.Build, sub_path: []const u8) *EnsureDirStep {
114+
const self = b.allocator.create(EnsureDirStep) catch @panic("OOM");
115+
self.* = .{
116+
.step = std.Build.Step.init(.{
117+
.id = .custom,
118+
.name = b.fmt("ensure {s}/", .{sub_path}),
119+
.owner = b,
120+
.makeFn = make,
121+
}),
122+
.sub_path = sub_path,
123+
};
124+
return self;
125+
}
126+
127+
fn make(step: *std.Build.Step, options: std.Build.Step.MakeOptions) anyerror!void {
128+
_ = options;
129+
const self: *EnsureDirStep = @fieldParentPtr("step", step);
130+
try step.owner.build_root.handle.createDirPath(step.owner.graph.io, self.sub_path);
131+
}
132+
};

build.zig.zon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.{
22
.name = .chilli,
3-
.version = "0.3.0",
3+
.version = "0.3.1",
44
.fingerprint = 0x6c259741ae4f5f73, // Changing this has security and trust implications.
55
.minimum_zig_version = "0.16.0",
66
.paths = .{

pyproject.toml

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,3 @@ dependencies = [
88
"python-dotenv (>=1.1.0,<2.0.0)",
99
"pre-commit (>=4.2.0,<5.0.0)"
1010
]
11-
12-
[project.optional-dependencies]
13-
dev = [
14-
"pytest (>=8.0.1,<9.0.0)",
15-
"pytest-cov (>=6.0.0,<7.0.0)",
16-
"pytest-mock (>=3.14.0,<4.0.0)",
17-
"pytest-asyncio (>=0.26.0,<0.27.0)",
18-
"mypy (>=1.11.1,<2.0.0)",
19-
"ruff (>=0.9.3,<1.0.0)",
20-
"icecream (>=2.1.4,<3.0.0)"
21-
]

0 commit comments

Comments
 (0)