Skip to content

Commit 48686f8

Browse files
spboyerCopilotJeffreyCA
authored andcommitted
docs: add PR review patterns to AGENTS.md (Azure#7301)
* docs: add PR review patterns to AGENTS.md Add lessons learned from team and Copilot reviews across PRs Azure#7290, Azure#7251, Azure#7250, Azure#7247, Azure#7236, Azure#7235, Azure#7202, Azure#7039 as agent instructions to prevent recurring review findings. New/expanded sections: - Error handling: ErrorWithSuggestion field completeness, telemetry service attribution, scope-agnostic messages, link/suggestion parity, stale data in polling loops - Architecture boundaries: pkg/project target-agnostic, extension docs separation, env var verification against source code - Output formatting: shell-safe quoted paths, consistent JSON types - Path safety: traversal validation, quoted paths in messages - Code organization: extract shared logic across scopes - Documentation standards: help text consistency, no dead references, PR description accuracy - Testing best practices: test YAML rules e2e, extract shared helpers, correct env vars (AZD_FORCE_TTY, NO_COLOR), TypeScript patterns, reasonable timeouts, cross-platform paths, test new JSON fields - CI / GitHub Actions: permissions blocks, PATH handling, cross-workflow artifacts, prefer ADO for secrets, no placeholder steps Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix Copilot instructions for code review and strengthen guidance on Go patterns (Azure#7320) * Fix Copilot instructions for code review and strengthen guidance on Go patterns * Update wording --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: JeffreyCA <jeffreychen@microsoft.com>
1 parent b06ca28 commit 48686f8

3 files changed

Lines changed: 92 additions & 3 deletions

File tree

.github/copilot-instructions.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Azure Developer CLI (azd) - Instructions for Copilot Chat and Copilot code review
2+
3+
For any work in this repository, especially for code reviews, you MUST read [cli/azd/AGENTS.md](../cli/azd/AGENTS.md) in its entirety (every line) first.

cli/azd/AGENTS.md

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Azure Developer CLI (azd) - Agent Instructions
22

3-
<!-- cspell:ignore Errorf Chdir azapi gofmt golangci stdlib -->
3+
<!-- cspell:ignore Errorf Chdir azapi gofmt golangci stdlib strconv Readdirnames -->
44

55
Instructions for AI coding agents working with the Azure Developer CLI.
66

@@ -154,24 +154,46 @@ func (a *myAction) Run(ctx context.Context) (*actions.ActionResult, error) {
154154
155155
- Commands can support multiple output formats via `--output` flag like `json` and`table`
156156
- Use structured output for machine consumption
157+
- **Shell-safe output**: When emitting shell commands in user-facing messages (e.g., `cd <path>`), quote paths that may contain spaces. Use `fmt.Sprintf("cd %q", path)` or conditionally wrap in quotes
158+
- **Consistent JSON types**: When adding fields to JSON output (`--output json`), match the types used by similar fields across commands. Don't mix `*time.Time` and custom timestamp wrappers (e.g., `*RFC3339Time`) in the same API surface
157159
158160
### Code Organization
159161
160162
- **Import order**: stdlib → external → azure/azd internal → local
161163
- **Complex packages**: Consider using `types.go` for shared type definitions (3+ types)
162164
- **Context propagation**: Pass `ctx context.Context` as the first parameter to functions that do I/O or may need cancellation
165+
- **Don't duplicate logic across scopes**: When similar logic exists for multiple deployment scopes (e.g., resource group + subscription), extract shared helpers (e.g., `filterActiveDeployments()`) instead of copying code between scope implementations
163166
164167
### Error Handling
165168
166169
- Wrap errors with `fmt.Errorf("context: %w", err)` to preserve the error chain
167170
- Consider using `internal.ErrorWithSuggestion` for straightforward, deterministic user-fixable issues
168171
- Handle context cancellations appropriately
172+
- **`ErrorWithSuggestion` completeness**: When returning `ErrorWithSuggestion`, populate **all** relevant fields (`Err`, `Suggestion`, `Message`, `Links`). `ErrorMiddleware` skips the YAML error-suggestion pipeline for errors already typed as `ErrorWithSuggestion`, so leaving fields empty means the user misses guidance that the YAML rule would have provided
173+
- **Telemetry service attribution**: Only set `error.service.name` (e.g., `"aad"`) when an external service actually returned the error. For locally-generated errors (e.g., "not logged in" state checks), don't attribute to an external service — use a local classification instead
174+
- **Scope-agnostic error messages**: Error messages and suggestions in `error_suggestions.yaml` should work across all deployment scopes (subscription, resource group, etc.). Use "target scope" or "deployment scope" instead of hardcoding "resource group"
175+
- **Match links to suggestion text**: If a suggestion mentions multiple tools (e.g., "Docker or Podman"), the `links:` list must include URLs for all of them. Don't mention options you don't link to
176+
- **Stale data in polling loops**: When polling for state changes (e.g., waiting for active deployments), refresh display data (names, counts) from each poll result rather than capturing it once at the start
177+
178+
### Architecture Boundaries
179+
180+
- **`ProjectManager` is target-agnostic**: `project_manager.go` should not contain service-target-specific logic (e.g., Container Apps details, Docker checks). Target-specific behavior belongs in the target implementations (e.g., `service_target_containerapp.go`) or in the `error_suggestions.yaml` pipeline. The project manager is an interface for service management and should not make assumptions about which target is running
181+
- **Extension-specific documentation**: Keep extension-specific environment variables and configuration documented in the extension's own docs, not in core azd reference docs, unless they are consumed by the core CLI itself
182+
- **Verify env vars against source**: When documenting environment variables, verify the actual parsing method in code — `os.LookupEnv` (presence-only) vs `strconv.ParseBool` (true/false) vs `time.ParseDuration` vs integer seconds. Document the expected format and default value accurately
183+
184+
### Path Safety
185+
186+
- **Validate derived paths**: When deriving directory names from user input or template paths, always validate the result is not `.`, `..`, empty, or contains path separators. These can cause path traversal outside the working directory
187+
- **Quote paths in user-facing output**: Shell commands in suggestions, follow-up messages, and error hints should quote file paths that may contain spaces
169188
170189
### Documentation Standards
171190
172191
- Public functions and types must have Go doc comments
173192
- Comments should start with the function/type name
174193
- Document non-obvious dependencies or assumptions
194+
- **Help text consistency**: When changing command behavior, update **all** related help text — `Short`, `Long`, custom description functions used by help generators, and usage snapshot files. Stale help text that contradicts the actual behavior is a common review finding
195+
- **No dead references**: Don't reference files, scripts, directories, or workflows that don't exist in the PR. If a README lists `scripts/generate-report.ts`, it must exist. If a CI table lists `eval-human.yml`, it must be included
196+
- **PR description accuracy**: Keep the PR description in sync with the actual implementation. If the description says "server-side filtering" but the code does client-side filtering, update the description
175197
176198
### Modern Go
177199
@@ -183,19 +205,72 @@ This project uses Go 1.26. Use modern standard library features:
183205
- **Range over integers**: `for i := range 10 { }`
184206
185207
### Modern Go Patterns (Go 1.26+)
208+
- Use `new(val)` not `x := val; &x` - returns pointer to any value.
209+
Go 1.26 extends `new()` to accept expressions, not just types.
210+
Type is inferred: `new(0) → *int`, `new("s") → *string`, `new(T{}) → *T`.
211+
DO NOT use `x := val; &x` pattern — always use `new(val)` directly.
212+
DO NOT use redundant casts like `new(int(0))` — just write `new(0)`.
213+
Before:
214+
```go
215+
timeout := 30
216+
debug := true
217+
cfg := Config{
218+
Timeout: &timeout,
219+
Debug: &debug,
220+
}
221+
```
222+
223+
After:
224+
```go
225+
cfg := Config{
226+
Timeout: new(30), // *int
227+
Debug: new(true), // *bool
228+
}
229+
```
230+
231+
- Use `errors.AsType[T](err)` not `errors.As(err, &target)`.
232+
ALWAYS use errors.AsType when checking if error matches a specific type.
233+
234+
Before:
235+
```go
236+
var pathErr *os.PathError
237+
if errors.As(err, &pathErr) {
238+
handle(pathErr)
239+
}
240+
```
241+
242+
After:
243+
```go
244+
if pathErr, ok := errors.AsType[*os.PathError](err); ok {
245+
handle(pathErr)
246+
}
247+
```
186248
187-
- Use `errors.AsType[*MyError](err)` instead of `var e *MyError; errors.As(err, &e)`
188249
- Use `slices.SortFunc(items, func(a, b T) int { return cmp.Compare(a.Name, b.Name) })` instead of `sort.Slice`
189250
- Use `slices.Clone(s)` instead of `append([]T{}, s...)`
190251
- Use `slices.Sorted(maps.Keys(m))` instead of collecting keys and sorting them separately
191252
- Use `http.NewRequestWithContext(ctx, method, url, body)` instead of `http.NewRequest(...)`
192-
- Use `new(expr)` instead of `to.Ptr(expr)`; `go fix ./...` applies this automatically
193253
- Use `wg.Go(func() { ... })` instead of `wg.Add(1); go func() { defer wg.Done(); ... }()`
194254
- Use `for i := range n` instead of `for i := 0; i < n; i++` for simple counted loops
195255
- Use `t.Context()` instead of `context.Background()` in tests
196256
- Use `t.Chdir(dir)` instead of `os.Chdir` plus a deferred restore in tests
197257
- Run `go fix ./...` before committing; CI enforces these modernizations
198258
259+
### Testing Best Practices
260+
261+
- **Test the actual rules, not just the framework**: When adding YAML-based error suggestion rules, write tests that exercise the rules end-to-end through the pipeline, not just tests that validate the framework's generic matching behavior
262+
- **Extract shared test helpers**: Don't duplicate test utilities across files. Extract common helpers (e.g., shell wrappers, auth token fetchers, CLI runners) into shared `test_utils` packages. Duplication across 3+ files should always be refactored
263+
- **Use correct env vars for testing**:
264+
- Non-interactive mode: `AZD_FORCE_TTY=false` (not `AZD_DEBUG_FORCE_NO_TTY`, which doesn't exist)
265+
- No-prompt mode: use the `--no-prompt` flag for core azd commands; `AZD_NO_PROMPT=true` is only used for propagating no-prompt into extension subprocesses
266+
- Suppress color: `NO_COLOR=1` — always set in test environments to prevent ANSI escape codes from breaking assertions
267+
- **TypeScript test patterns**: Use `catch (e: unknown)` with type assertions, not `catch (e: any)` which bypasses strict mode
268+
- **Reasonable timeouts**: Set test timeouts proportional to expected execution time. Don't use 5-minute timeouts for tests that shell out to `azd --help` (which completes in seconds)
269+
- **Efficient directory checks**: To check if a directory is empty, use `os.Open` + `f.Readdirnames(1)` instead of `os.ReadDir` which reads the entire listing into memory
270+
- **Cross-platform paths**: When resolving binary paths in tests, handle `.exe` suffix on Windows (e.g., `azd` vs `azd.exe` via `process.platform === "win32"`)
271+
- **Test new JSON fields**: When adding fields to JSON command output (e.g., `expiresOn` in `azd auth status --output json`), add a test asserting the field's presence and format
272+
- **No unused dependencies**: Don't add npm/pip packages that aren't imported anywhere. Remove dead `devDependencies` before submitting
273+
199274
## MCP Tools
200275
201276
Tools follow `server.ServerTool` interface from `github.com/mark3labs/mcp-go/server`:
@@ -227,3 +302,13 @@ Feature-specific docs are in `docs/` — refer to them as needed. Some key docs
227302
- `docs/extensions/extension-framework.md` - Extension development using gRPC extension framework
228303
- `docs/style-guidelines/guiding-principles.md` - Design principles
229304
- `docs/tracing-in-azd.md` - Tracing/telemetry guidelines
305+
306+
## CI / GitHub Actions
307+
308+
When creating or modifying GitHub Actions workflows:
309+
310+
- **Always declare `permissions:`** explicitly with least-privilege (e.g., `contents: read`). All workflows in the repo should have this block for consistency
311+
- **Don't overwrite `PATH`** using `${{ env.PATH }}` — it's not defined in GitHub Actions expressions and will wipe the real PATH. Use `echo "$DIR" >> $GITHUB_PATH` instead
312+
- **Cross-workflow artifacts**: `actions/download-artifact@v4` without `run-id` only downloads artifacts from the *current* workflow run. Cross-workflow artifact sharing requires `run-id` and `repository` parameters
313+
- **Prefer Azure DevOps pipelines** for jobs that need secrets or Azure credentials — the team uses internal ADO pipelines for authenticated workloads in this public repo
314+
- **No placeholder steps**: Don't add workflow steps that echo "TODO" or list directories without producing output. If downstream steps depend on generated files, implement the generation or remove the dependency

eng/pipelines/release-cli.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pr:
3939
- cli/azd/extensions/**
4040
- cli/azd/.vscode/cspell.yaml
4141
- cli/azd/.vscode/cspell-azd-dictionary.txt
42+
- cli/azd/AGENTS.md
4243

4344
extends:
4445
template: /eng/pipelines/templates/stages/1es-redirect.yml

0 commit comments

Comments
 (0)