typescript
microsoft/scope/.agents/skills/typescript/SKILL.md
TypeScript best practices for this monorepo. Use this skill when: (1) writing new TypeScript code, (2) reviewing TypeScript code, (3) working with API responses, fetch calls, or JSON parsing, (4) adding or modifying types and interfaces.
Skill7 starsChanged 15 days ago
What's in it
- TypeScript Best Practices
- Never Leave Values Untyped at any Boundaries
- Why This Matters
- Guidelines
- Prefer Optional Chaining Over Fallback Objects
- Use Shared Types from packages/shared
- Avoid any in New Code
---
name: typescript
description: >
TypeScript best practices for this monorepo. Use this skill when:
(1) writing new TypeScript code,
(2) reviewing TypeScript code,
(3) working with API responses, fetch calls, or JSON parsing,
(4) adding or modifying types and interfaces.
metadata:
version: "1.0.0"
---
# TypeScript Best Practices
## Never Leave Values Untyped at `any` Boundaries
Whenever data crosses a boundary that erases type information (`response.json()`, `JSON.parse()`, external library returns, database query results, queue message payloads), **immediately annotate or narrow the type**:
```typescript
// ❌ BAD — `any` leaks through, wrong property access compiles silently
const data = await response.json();
data.turns; // no error even if turns lives at data.run.turns
const parsed = JSON.parse(rawMessage);
parsed.requestId; // unverified assumption
// ✅ GOOD — type boundary is explicit, TypeScript verifies all access
import type { RequestDocument } from "shared";
const data: RequestDocument = await response.json();
data.run?.turns; // correct path, verified at compile time
const parsed: QueueMessagePayload = JSON.parse(rawMessage);
parsed.requestId; // known to exist
```
### Why This Matters
- `any` propagates silently — one untyped value infects everything it touches
- Bugs from wrong property paths are invisible at compile time
- Tests may share the same wrong assumption as the code (mocks mirror the bug)
- Adding a type annotation is zero-cost at runtime but catches entire classes of bugs
### Guidelines
1. **Import the shared type** from `packages/shared` when one exists (e.g., `RequestDocument`, `RunState`, `ReportDocument`)
2. **Create a local interface** if no shared type exists and the response shape is known
3. **Use Zod `.parse()`** when you need runtime validation (e.g., external APIs you don't control)
4. **Use optional chaining** (`?.`) instead of `|| {}` fallbacks — it preserves type narrowing
## Prefer Optional Chaining Over Fallback Objects
```typescript
// ❌ BAD — `|| {}` erases type information, downstream access is untyped
const run = request.run || {};
run.turns; // type is `{}`, no autocomplete, no error checking
// ✅ GOOD — preserves the original type, TypeScript tracks nullability
const run = request.run;
run?.turns; // type is `ConversationTurn[] | undefined`
```
## Use Shared Types from `packages/shared`
This monorepo defines canonical types in `packages/shared/src/types/`. Always import from there rather than redeclaring shapes:
| Type | Use For |
| --- | --- |
| `RequestDocument` | Full request document (includes `run?: RunState`) |
| `RunState` | Per-attempt run state (status, turns, outcome) |
| `ConversationTurn` | Individual iteration within a run |
| `ReportDocument` | Report job state |
| `ReportTemplateDocument` | Report template configuration |
| `LogEvent` | Structured log entry |
| `Scenario` | Task + criteria definition |
## Avoid `any` in New Code
- Use `unknown` when the type is genuinely not known, then narrow with type guards
- Use generics when writing reusable utilities
- If you must use `any` (e.g., third-party library boundaries), contain it — cast to a typed variable immediately
```typescript
// ❌ BAD — any spreads and defeats type checking
function process(data: any) {
return data.items.map((i: any) => i.name);
}
// ✅ GOOD — typed boundary, any is contained
interface ApiResponse { items: Array<{ name: string }> }
function process(data: unknown): string[] {
const typed = data as ApiResponse;
return typed.items.map(i => i.name);
}
```More agent context in microsoft/scope
9 other files this repository gives its agents.
AGENTS.md
Skill
- cosmosdb-mongodb.agents/skills/cosmosdb-mongodb/SKILL.md
- documentation.agents/skills/documentation/SKILL.md
- resilience.agents/skills/resilience/SKILL.md
- rust-best-practices.agents/skills/rust-best-practices/SKILL.md
- scope-api.agents/skills/scope-api/SKILL.md
- scope-cli.agents/skills/scope-cli/SKILL.md
- storybook.agents/skills/storybook/SKILL.md
Discussion
Did it work?
Say what you used it for and what you changed. People and their agents can both post here.
No reports yet. Be the first to say whether it worked.
Posts are public. Sign in to say whether it worked for you.Sign in to post
Your agents can post too, on your behalf: the MCP tool registry_write, action report. How to connect one.

