This file provides guidance to AI coding agents when working with code in this repository.
LaunchDarkly CLI (ldcli) — a Go CLI for managing LaunchDarkly feature flags. Built with Cobra/Viper, distributed via Homebrew, Docker, NPM, and GitHub Releases.
Write all prose in Simple English. Simple English is plain English that follows the rules of ASD-STE100 Simplified Technical English. The full rules are in .agents/skills/simple-english/SKILL.md. Read that file before you write or rewrite a document.
The rules apply to this text:
- Replies to the user
- Markdown files, such as
README.mdandCONTRIBUTING.md - Pull request titles and descriptions
- Commit messages
- New code comments, CLI help text, and error messages
Do not change these items:
- Code, identifiers, commands, flags, file paths, and quoted errors
- Generated files, such as
CHANGELOG.mdandcmd/resources/resource_cmds.go - Text that your task does not touch
Agents break these rules most often:
- Write short sentences. Use 20 words at most for an instruction and 25 words at most for a description.
- Use active voice and simple tenses. Write
The command deleted the row, notThe row has been deleted. - Use
can,will, ormust. Do not useshould,would,may,might, orcould. - Put a condition before its command: "If the build fails, read the log."
- Use one word for one meaning in a document. For example, use
configurationevery time, notconfigin one place andsettingsin another. - Define a technical term the first time that you use it.
- Do not use contractions, semicolons, or em dashes.
- State facts. Do not add words such as
robust,seamless, orcrucial. - In a reply, put the answer in the first sentence. Write prose, with no headers, bold text, lists, or tables.
Hooks load these rules at the start of a session. They also lint the Markdown files that an agent writes. The hooks are advisory, so they never block an action. Each hook runs .agents/skills/simple-english/scripts/hook.py, which needs python3.
| Tool | Configuration | What the hooks do |
|---|---|---|
| Cursor | .cursor/hooks.json |
Load the rules at session start. Lint each Markdown file after a write. |
| Claude Code | .claude/settings.json |
Load the rules at session start. Lint each Markdown file after a write. Report bold text, headers, lists, and em dashes in each reply. |
| Codex | .codex/hooks.json |
Load the rules at session start. |
These limits apply:
- Cursor Cloud Agents do not run session start hooks. They get the rules from this file, and the Markdown lint still runs.
- Cursor also runs the hooks in
.claude/settings.json. The script finds this case and runs only the Cursor hooks. - Codex runs project hooks only in a trusted project. Codex also asks you to approve each hook. Open
/hooksto approve it. - The lint reports only the lines that differ from the last commit. A new file gets a full lint.
- The lint skips
CHANGELOG.md, the skill folder, and files outside the repository.
To turn off the hooks, set SIMPLE_ENGLISH_HOOKS=off. To test the hooks, run python3 .agents/skills/simple-english/scripts/test_hook.py. The file .agents/skills/simple-english/UPSTREAM.md gives the source of the skill and the steps to update it.
make build # Build binary as ./ldcli
make test # Run all tests (go test ./...)
go test ./path/to/pkg # Run tests for a specific package
make generate # Regenerate code from OpenAPI spec (go generate ./...)
make vendor # Tidy and vendor dependencies
make install-hooks # Install git pre-commit hooks
make openapi-spec-update # Download latest OpenAPI spec and regenerate codeResource commands are auto-generated from the LaunchDarkly OpenAPI spec (ld-openapi.json):
- Generator:
cmd/resources/gen_resources.go(build tag:gen_resources) - Template:
cmd/resources/resource_cmds.tmpl - Output:
cmd/resources/resource_cmds.go(~613KB, do not edit manually) - Trigger:
//go:generatedirective incmd/root.go
The dev server API is also generated: internal/dev_server/api/server.gen.go (via oapi-codegen).
Entry point: main.go → cmd.Execute(version) → cmd/root.go (Cobra root command)
Command layer (cmd/):
- Each subcommand (flags, members, config, login, dev-server, sourcemaps, resources) has its own package
- Resource commands are generated; custom commands are hand-written
- Analytics tracking via
PersistentPreRunhooks
Internal packages (internal/):
- Each domain package (flags, environments, members, projects, resources, dev_server) exposes a
Clientinterface for dependency injection internal/dev_server/— local dev server with SQLite storage, embedded React UI, and LaunchDarkly SDK integrationinternal/config/— manages CLI configuration via$XDG_CONFIG_HOME/ldcli/config.ymlinternal/output/— response formatting (JSON/plaintext)
Configuration precedence: CLI flags → environment variables (prefix LD_) → config file
- Add command to root via
cmd.AddCommandinNewRootCommand()incmd/root.go - Update usage template in
getUsageTemplate()incmd/root.go - Add analytics instrumentation via
PersistentPreRuncallingtracker.SendCommandRunEvent
Located at internal/dev_server/ui/ — React 18 + TypeScript + Vite, embedded into the Go binary.
cd internal/dev_server/ui
npm ci
npm test # Vitest
npm run lint # ESLint
npm run build # Production build (checked into repo)
- Go tests use
testifyfor assertions andgo.uber.org/mockfor mocking - Mock generation via
mockgen - Test data in
cmd/resources/test_data/andcmd/config/testdata/
Installed via make install-hooks. Checks:
go fmtformattinggo.mod/go.sumtidiness- Dev server UI tests and build (requires npm)
- Go:
golangci-lint(v1.63.4) via pre-commit - Frontend: ESLint + Prettier