The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT", and "MAY" in this document are to be interpreted as described in RFC 2119.
clitest (CLI Test) — a Go CLI tool that runs declarative .clitest test files against shell commands. Single binary, zero dependencies.
- Module:
github.com/sleipi/cli-t - Binary output:
./clitest(repo root) - Spec:
SPEC.md— the authoritative syntax reference; keep it in sync with code changes.
- Go
- You MUST work on a feature branch — never commit directly to main.
- You MUST follow the Conventional Commits 1.0.0 specification for all commit messages.
- You MUST write code using a TDD approach — write/update tests before implementation.
- You MUST write E2E tests (in
test/e2e/) for any new or changed behaviour. - You MUST NOT use
@-prefixed directive names (e.g.@defer,@poll,@timeout) in commit messages or PR titles — GitHub interprets them as user mentions. Use backticks (`@defer`) or omit the@prefix (e.g. "support defer directive").
ROADMAP.mdis the source of truth for planned and completed features.- Each planned item SHOULD have a corresponding GitHub Issue — link it inline:
[#N](url). - You MUST update
ROADMAP.mdwhen completing a planned feature — move the item from "Planned" to "Completed" and mark it[x]. Newest completed items MUST be placed at the top of the "Completed" list. - When creating new issues from roadmap items, use these labels:
enhancement— new features and functionalityeco— ecosystem (IDE plugins, docs, packaging, distribution)tbd— needs further specification/discussion
- Items without a GitHub Issue are parked at the bottom of the "Planned" list.
- Duplicate items MUST be consolidated.
Branch names MUST follow the format <type>/<short-description>.
Types: feat/, fix/, docs/, refactor/, test/, chore/
Rules:
- You MUST use lowercase and hyphens as word separators (e.g.
feat/add-capture-support). - You SHOULD keep names short but descriptive.
- You MAY include an issue number (e.g.
feat/42-capture-support). - You MUST NOT use special characters beyond hyphens and slashes.
make build # go build -o clitest ./cmd/clitest/
make test # go test ./...
make lint # golangci-lint (static analysis, allow failure)
make e2e # build + run ./clitest test/e2e/
make examples # build + run ./clitest examples/
make all # test + lint + e2e + examples (use this to verify changes)Always run make all after changes — unit tests alone won't catch parser/CLI regressions.
cmd/clitest/main.go CLI entrypoint
cmd/clitest/root.go Cobra command setup, flags, orchestration (worker pools)
cmd/clitest/run.go Entry orchestration with display coupling, loadAndParse
cmd/clitest/flags.go varMap pflag type for --var
internal/display/ Rendering: ProgressDisplay, VerboseDisplay, ANSI colors, header/summary/failure output
internal/resolve/ File discovery: glob expansion, recursive dir walk, path resolution
internal/filter/ Group tag filtering (include/exclude)
internal/vars/ Variable substitution (--var, captures, env expansion)
internal/executor/ Entry execution, background processes, defer handling
internal/parser/ .clitest format parser (entry builder, assert/capture parsing)
internal/runner/ Command execution via sh -c, captures stdout/stderr/exit/duration
internal/assert/ Predicate evaluation engine (queries + predicates + negation)
internal/types/ Shared types: Entry, Assert, Capture, File, Directives
examples/*.clitest User-facing usage examples (also validated via `make examples`)
test/e2e/syntax/ E2E tests for .clitest syntax (asserts, captures, directives)
test/e2e/output/ E2E tests for header/footer/summary output and progress bar
test/e2e/options/ E2E tests for CLI flags (--var, --parallel, -v, --group)
test/e2e/resolve/ E2E tests for file discovery (recursive, glob, skip warnings)
test/e2e/background/ E2E tests for EXIT NEVER, @defer, @poll
test/_fixtures/ Shared test fixtures (e.g. intentionally failing .clitest files)
- TDD: write/update
*_test.gobefore or alongside implementation. test/e2e/contains all E2E tests of the clitest tool itself — keep them green viamake e2e.examples/contains user-facing usage demonstrations — validated viamake examples.- Parser is hand-rolled (no parser generator). Entries separated by blank lines; sections by
[Name]headers. - Values in asserts:
"quoted"strings,/regex/literals, or bare tokens. SeeunquoteValuein parser. - Runner always uses
sh -c; commands can span multiple lines (trailing\continuation). - Variable substitution (
{{name}}) happens incmd/clitest/run.goviainternal/varsbefore parsing — it's a simple string replace, not part of the parser. - File discovery is recursive by default (
--no-recursiveto disable). Non-.clitestfiles are skipped with a warning to stderr. - Glob patterns in arguments (quoted, e.g.
"examples/*.clitest") are expanded by clitest itself, not the shell. The original pattern is preserved in the header output. EXIT NEVERmarks background processes — runner starts them without waiting for exit, polls asserts until pass or@timeout.@deferentries are cleanup (not tests): collected during parsing, executed LIFO at file end, errors logged but not failed.@poll MSsets the polling interval forEXIT NEVERasserts (default 100ms).pidis a capture query only available onEXIT NEVERentries.
Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
Tradeoff: These guidelines bias toward caution over speed. For trivial tasks, use judgment.
Don't assume. Don't hide confusion. Surface tradeoffs.
Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them - don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
Minimum code that solves the problem. Nothing speculative.
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
Touch only what you must. Clean up only your own mess.
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.
When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
Define success criteria. Loop until verified.
Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"
For multi-step tasks, state a brief plan:
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
These guidelines are working if: fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.