Skip to content

Commit b07b072

Browse files
committed
docs: update AGENTS.md with patterns from span commands work
Major updates based on lessons learned during PR #393: 1. CLI Commands section: Replace deprecated stdout.write() pattern with the current async generator + CommandOutput + OutputConfig pattern. Add explicit rules about buildCommand import source. 2. New sections: Positional Arguments (parseSlashSeparatedArg pattern), Markdown Rendering (renderMarkdown/colorTag rules, plainSafeMuted), List Command Pagination (cursor infrastructure), ID Validation (hex-id.ts validators), Sort Convention ('date' not 'time'), SKILL.md (generate:skill, descriptive placeholders). 3. Architecture tree: Add missing directories (span/, trace/, log/, trial/, cli/, api/), files (command.ts, hex-id.ts, trace-id.ts, pagination.ts, time-utils.ts, markdown.ts, trace.ts, table.ts), and fix stale descriptions (api-client.ts is barrel, not ky-based). 4. List Command Infrastructure: Add standalone list command pattern (span list, trace list) as third tier alongside buildOrgListCommand and dispatchOrgScopedList. 5. Imports: Fix stale @stricli/core import in example.
1 parent efbeff1 commit b07b072

File tree

1 file changed

+124
-10
lines changed

1 file changed

+124
-10
lines changed

AGENTS.md

Lines changed: 124 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,31 @@ cli/
109109
│ │ ├── issue/ # list, view, explain, plan
110110
│ │ ├── org/ # list, view
111111
│ │ ├── project/ # list, view
112+
│ │ ├── span/ # list, view
113+
│ │ ├── trace/ # list, view, logs
114+
│ │ ├── log/ # list, view
115+
│ │ ├── trial/ # list, start
116+
│ │ ├── cli/ # fix, upgrade, feedback, setup
112117
│ │ ├── api.ts # Direct API access command
113118
│ │ └── help.ts # Help command
114119
│ ├── lib/ # Shared utilities
115-
│ │ ├── api-client.ts # Sentry API client (ky-based)
120+
│ │ ├── command.ts # buildCommand wrapper (telemetry + output)
121+
│ │ ├── api-client.ts # Barrel re-export for API modules
122+
│ │ ├── api/ # Domain API modules
123+
│ │ │ ├── infrastructure.ts # Shared helpers, types, raw requests
124+
│ │ │ ├── organizations.ts
125+
│ │ │ ├── projects.ts
126+
│ │ │ ├── issues.ts
127+
│ │ │ ├── events.ts
128+
│ │ │ ├── traces.ts # Trace + span listing
129+
│ │ │ ├── logs.ts
130+
│ │ │ ├── seer.ts
131+
│ │ │ └── trials.ts
116132
│ │ ├── region.ts # Multi-region resolution
117133
│ │ ├── telemetry.ts # Sentry SDK instrumentation
118134
│ │ ├── sentry-urls.ts # URL builders for Sentry
135+
│ │ ├── hex-id.ts # Hex ID validation (32-char + 16-char span)
136+
│ │ ├── trace-id.ts # Trace ID validation wrapper
119137
│ │ ├── db/ # SQLite database layer
120138
│ │ │ ├── instance.ts # Database singleton
121139
│ │ │ ├── schema.ts # Table definitions
@@ -125,6 +143,7 @@ cli/
125143
│ │ │ ├── user.ts # User info cache
126144
│ │ │ ├── regions.ts # Org→region URL cache
127145
│ │ │ ├── defaults.ts # Default org/project
146+
│ │ │ ├── pagination.ts # Cursor pagination storage
128147
│ │ │ ├── dsn-cache.ts # DSN resolution cache
129148
│ │ │ ├── project-cache.ts # Project data cache
130149
│ │ │ ├── project-root-cache.ts # Project root cache
@@ -154,7 +173,12 @@ cli/
154173
│ │ │ ├── json.ts # JSON output
155174
│ │ │ ├── output.ts # Output utilities
156175
│ │ │ ├── seer.ts # Seer AI response formatting
157-
│ │ │ └── colors.ts # Terminal colors
176+
│ │ │ ├── colors.ts # Terminal colors
177+
│ │ │ ├── markdown.ts # Markdown → ANSI renderer
178+
│ │ │ ├── trace.ts # Trace/span formatters
179+
│ │ │ ├── time-utils.ts # Shared time/duration utils
180+
│ │ │ ├── table.ts # Table rendering
181+
│ │ │ └── log.ts # Log entry formatting
158182
│ │ ├── oauth.ts # OAuth device flow
159183
│ │ ├── errors.ts # Error classes
160184
│ │ ├── resolve-target.ts # Org/project resolution
@@ -197,34 +221,122 @@ cli/
197221

198222
### CLI Commands (Stricli)
199223

200-
Commands use `@stricli/core`.
224+
Commands use [Stricli](https://bloomberg.github.io/stricli/docs/getting-started/principles) wrapped by `src/lib/command.ts`.
201225

202-
**Stricli Documentation**: https://bloomberg.github.io/stricli/docs/getting-started/principles
226+
**CRITICAL**: Import `buildCommand` from `../../lib/command.js`, **NEVER** from `@stricli/core` directly — the wrapper adds telemetry, `--json`/`--fields` injection, and output rendering.
203227

204228
Pattern:
205229

206230
```typescript
207-
import { buildCommand } from "@stricli/core";
231+
import { buildCommand } from "../../lib/command.js";
208232
import type { SentryContext } from "../../context.js";
233+
import { CommandOutput } from "../../lib/formatters/output.js";
209234

210235
export const myCommand = buildCommand({
211236
docs: {
212237
brief: "Short description",
213238
fullDescription: "Detailed description",
214239
},
240+
output: {
241+
human: formatMyData, // (data: T) => string
242+
jsonTransform: jsonTransformMyData, // optional: (data: T, fields?) => unknown
243+
jsonExclude: ["humanOnlyField"], // optional: strip keys from JSON
244+
},
215245
parameters: {
216246
flags: {
217-
json: { kind: "boolean", brief: "Output as JSON", default: false },
218247
limit: { kind: "parsed", parse: Number, brief: "Max items", default: 10 },
219248
},
220249
},
221-
async func(this: SentryContext, flags) {
222-
const { process } = this;
223-
// Implementation - use process.stdout.write() for output
250+
async *func(this: SentryContext, flags) {
251+
const data = await fetchData();
252+
yield new CommandOutput(data);
253+
return { hint: "Tip: use --json for machine-readable output" };
224254
},
225255
});
226256
```
227257

258+
**Key rules:**
259+
- Functions are `async *func()` generators — yield `new CommandOutput(data)`, return `{ hint }`.
260+
- `output.human` receives the same data object that gets serialized to JSON — no divergent-data paths.
261+
- The wrapper auto-injects `--json` and `--fields` flags. Do NOT add your own `json` flag.
262+
- Do NOT use `stdout.write()` or `if (flags.json)` branching — the wrapper handles it.
263+
264+
### Positional Arguments
265+
266+
Use `parseSlashSeparatedArg` from `src/lib/arg-parsing.ts` for the standard `[<org>/<project>/]<id>` pattern. Required identifiers (trace IDs, span IDs) should be **positional args**, not flags.
267+
268+
```typescript
269+
import { parseSlashSeparatedArg, parseOrgProjectArg } from "../../lib/arg-parsing.js";
270+
271+
// "my-org/my-project/abc123" → { id: "abc123", targetArg: "my-org/my-project" }
272+
const { id, targetArg } = parseSlashSeparatedArg(first, "Trace ID", USAGE_HINT);
273+
const parsed = parseOrgProjectArg(targetArg);
274+
// parsed.type: "auto-detect" | "explicit" | "project-search" | "org-all"
275+
```
276+
277+
Reference: `span/list.ts`, `trace/view.ts`, `event/view.ts`
278+
279+
### Markdown Rendering
280+
281+
All non-trivial human output must use the markdown rendering pipeline:
282+
283+
- Build markdown strings with helpers: `mdKvTable()`, `colorTag()`, `escapeMarkdownCell()`, `renderMarkdown()`
284+
- **NEVER** use raw `muted()` / chalk in output strings — use `colorTag("muted", text)` inside markdown
285+
- Tree-structured output (box-drawing characters) that can't go through `renderMarkdown()` should use the `plainSafeMuted` pattern: `isPlainOutput() ? text : muted(text)`
286+
- `isPlainOutput()` precedence: `SENTRY_PLAIN_OUTPUT` > `NO_COLOR` > `FORCE_COLOR` > `!isTTY`
287+
288+
Reference: `formatters/trace.ts` (`formatAncestorChain`), `formatters/human.ts` (`plainSafeMuted`)
289+
290+
### List Command Pagination
291+
292+
All list commands with API pagination MUST use the shared cursor infrastructure:
293+
294+
```typescript
295+
import { LIST_CURSOR_FLAG } from "../../lib/list-command.js";
296+
import {
297+
buildPaginationContextKey, resolveOrgCursor,
298+
setPaginationCursor, clearPaginationCursor,
299+
} from "../../lib/db/pagination.js";
300+
301+
export const PAGINATION_KEY = "my-entity-list";
302+
303+
// In buildCommand:
304+
flags: { cursor: LIST_CURSOR_FLAG },
305+
aliases: { c: "cursor" },
306+
307+
// In func():
308+
const contextKey = buildPaginationContextKey("entity", `${org}/${project}`, {
309+
sort: flags.sort, q: flags.query,
310+
});
311+
const cursor = resolveOrgCursor(flags.cursor, PAGINATION_KEY, contextKey);
312+
const { data, nextCursor } = await listEntities(org, project, { cursor, ... });
313+
if (nextCursor) setPaginationCursor(PAGINATION_KEY, contextKey, nextCursor);
314+
else clearPaginationCursor(PAGINATION_KEY, contextKey);
315+
```
316+
317+
Show `-c last` in the hint footer when more pages are available. Include `nextCursor` in the JSON envelope.
318+
319+
Reference template: `trace/list.ts`, `span/list.ts`
320+
321+
### ID Validation
322+
323+
Use shared validators from `src/lib/hex-id.ts`:
324+
- `validateHexId(value, label)` — 32-char hex IDs (trace IDs, log IDs). Auto-strips UUID dashes.
325+
- `validateSpanId(value)` — 16-char hex span IDs. Auto-strips dashes.
326+
- `validateTraceId(value)` — thin wrapper around `validateHexId` in `src/lib/trace-id.ts`.
327+
328+
All normalize to lowercase. Throw `ValidationError` on invalid input.
329+
330+
### Sort Convention
331+
332+
Use `"date"` for timestamp-based sort (not `"time"`). Export sort types from the API layer (e.g., `SpanSortValue` from `api/traces.ts`), import in commands. This matches `issue list`, `trace list`, and `span list`.
333+
334+
### SKILL.md
335+
336+
- Run `bun run generate:skill` after changing any command parameters, flags, or docs.
337+
- CI check `bun run check:skill` will fail if SKILL.md is stale.
338+
- Positional `placeholder` values must be descriptive: `"org/project/trace-id"` not `"args"`.
339+
228340
### Zod Schemas for Validation
229341

230342
All config and API types use Zod schemas:
@@ -320,7 +432,7 @@ await setAuthToken(token, expiresIn);
320432
321433
```typescript
322434
import { z } from "zod";
323-
import { buildCommand } from "@stricli/core";
435+
import { buildCommand } from "../../lib/command.js";
324436
import type { SentryContext } from "../../context.js";
325437
import { getAuthToken } from "../../lib/config.js";
326438
```
@@ -339,6 +451,8 @@ Key rules when writing overrides:
339451
- `resolveCursor()` must be called **inside** the `org-all` override closure, not before `dispatchOrgScopedList`, so that `--cursor` validation errors fire correctly for non-org-all modes.
340452
- `handleProjectSearch` errors must use `"Project"` as the `ContextError` resource, not `config.entityName`.
341453
454+
3. **Standalone list commands** (e.g., `span list`, `trace list`) that don't use org-scoped dispatch wire pagination directly in `func()`. See the "List Command Pagination" section above for the pattern.
455+
342456
## Commenting & Documentation (JSDoc-first)
343457
344458
### Default Rule

0 commit comments

Comments
 (0)