Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 41 additions & 4 deletions .agent/rules/porting.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,33 @@ const nullishString = z.string().nullish().transform(v => v ?? '');
const goString = z.string().nullish().transform(v => v ?? '');
```

## Critical Thinking

**Do not blindly copy.** Before porting each function, type, or test, ask:

1. **Does TypeScript need it?** Some Go code exists only because Go requires
explicit opt-in for features that TypeScript provides natively. For example,
Go requires an explicit `Unwrap() error` method for error chain traversal;
TypeScript gets this for free via `Error.cause`. Do not port such code.
2. **Does the test validate our code or the language?** If a Go test verifies
behaviour that is built into the TypeScript runtime (e.g. that `Error.cause`
preserves a reference), skip it — it tests the language, not our
implementation. Explain the skip to the user.

When in doubt, ask the user before porting.

## Factory Functions vs Constructors

When a Go function can return `nil` (e.g. `FromHTTPError` returns `nil` for 2xx
status codes), port it as a **factory function** that returns `T | undefined`.
Constructors in TypeScript always return an instance and cannot express "no
result." Use a standalone function or a static method on the class.

## Tests

Port **all** test cases from the Go SDK. Do not selectively skip tests. When a
Go test case cannot be directly ported (e.g. because of a language difference
like `json.RawMessage` vs already-parsed JSON), explain the omission to the
user and get confirmation before skipping.
Port **all** test cases from the Go SDK unless they fall under the "Critical
Thinking" exceptions above. Do not selectively skip tests without explaining
the reason to the user.

Use the same test structure as the Go SDK. Go table-driven tests map to
Vitest's `it.each`:
Expand All @@ -101,6 +122,22 @@ const testCases: {name: string; input: number; want: number}[] = [
it.each(testCases)('$name', ({input, want}) => { ... });
```

## No Go References in Code

**Never reference Go in code or comments.** This includes variable names,
function names, inline comments, and JSDoc. The TypeScript codebase must read
as if Go does not exist. Do not write comments like "mirrors Go's
tc.want.httpErr" or "equivalent of Go's errors.As."

```typescript
// Bad — references the Go implementation.
// Input HTTP fields are always preserved (mirrors Go's
// tc.want.httpErr = &httpError{statusCode, header, body}).

// Good — describes what the code does without referencing Go.
// Input HTTP fields are always preserved.
```

## Deviations from Go

When the TypeScript implementation must differ from Go (e.g. different function
Expand Down
42 changes: 42 additions & 0 deletions .agent/rules/testing.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,48 @@ expect(token.accessToken).toBe('dapi...');
expect(result).toStrictEqual({host: 'example.com', port: 443});
```

## Never Silently Skip Assertions

Do not use `return` in a test path where a failure should be reported. If a
precondition fails, use `expect.fail()` so the test fails loudly instead of
silently passing with no assertions.

```typescript
// Good — fails loudly if got is unexpectedly undefined.
if (got === undefined) {
expect.fail('expected a result');
}

// Bad — silently skips all remaining assertions.
if (got === undefined) {
return;
}
```

## Test All Outputs

When a function returns an object, verify all its fields — not just the ones
that seem interesting. Do not skip fields because they appear "obvious" or are
"just pass-through." If the function sets a field, the test should check it.

## Use Real Types for Expected Values

When the expected output is a class instance, use that class as the type of
the `want` field in test tables. Do not invent anonymous object types that
duplicate the class shape.

```typescript
// Good — want is a real APIError.
const testCases: {desc: string; want?: APIError}[] = [
{desc: 'not found', want: new APIError({code: Code.NOT_FOUND, ...})},
];

// Bad — anonymous type duplicates APIError's shape.
const testCases: {desc: string; want?: {code: Code; message: string}}[] = [
{desc: 'not found', want: {code: Code.NOT_FOUND, message: '...'}},
];
```

## Independence

Each test must be independent. Do not rely on execution order. Clean up side
Expand Down
6 changes: 6 additions & 0 deletions .agent/rules/typescript.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,12 @@ Avoid private static methods; prefer module-level functions instead. Never use

Always include parentheses in constructor calls: `new Foo()`, not `new Foo`.

### 7.7 API Surface Minimization

Default to the smallest public API surface. Only export from `index.ts` what
consumers need. Internal types (options interfaces, schemas, helper functions)
must not be re-exported.

---

## 8. Control Flow
Expand Down
12 changes: 11 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,14 @@ npm run clean
with a period.
6. **Back up claims.** When proposing a design decision or asserting a
convention, provide concrete references (documentation, API links). Do not
state something is "idiomatic" or "standard" without evidence.
state something is "idiomatic" or "standard" without evidence. Use
authoritative primary sources (language specs, official documentation) — not
blog posts, archived repositories, or npm packages.
7. **Stay in scope.** Only change what was asked. Each change should be
reviewable in isolation.
8. **Never silently remove code.** If a change requires deleting existing
code or tests, explain what is being removed and why **before** proceeding.
Get explicit confirmation from the user.
9. **Match existing patterns.** Before writing new code, check existing code
for established patterns. Do not invent new conventions
when the codebase already has one.