Skip to content
Open
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add `pr-workflow` skills (commit-discipline, diff-audit, pr-review-discipline + knowledge), `coding` skills (ts2589-workaround + TypeScript knowledge), and `general` skills (scope-lock, specifications-as-guardrails, diagnose-blockers)

## [0.1.0]

### Added
Expand Down
Empty file.
30 changes: 30 additions & 0 deletions domains/coding/knowledge/cascade-refactoring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
name: cascade-refactoring
domain: coding
description: Only modify files that would fail to compile without the change. Verify with a blast-radius check before committing.
---

# Cascade Refactoring Anti-Pattern

When making a targeted change in one layer of a system, do not follow type/lint feedback across the dependency graph into "while I'm here" cleanups.

## Rule

Only modify files that would **fail to compile** without the change. If a consumer still compiles with the old types, leave it alone.

## Pattern

A change to one layer triggers type errors in adjacent layers. The correct response is to fix only what breaks. The wrong response is to follow each error transitively and refactor downstream consumers.

Those downstream changes compile, but they're cosmetic refactoring disguised as necessary fixes — expanding scope without expanding value.

## Blast-Radius Check

Before committing, list every modified file and justify it against the task description:

- Does this file fail to compile without the change? → Keep
- Does it compile fine with the old types? → Revert

## Signal

If a diff touches files from multiple unrelated layers for a task scoped to one layer, something leaked. Common pattern: a "collection" file change pulling in "reporting" or "display" file changes.
37 changes: 37 additions & 0 deletions domains/coding/knowledge/spike-integration-boundaries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
name: spike-integration-boundaries
domain: coding
description: Test the riskiest integration seam in isolation with a 10-line spike before building the full multi-system script.
---

# Spike Hard Integrations Before Committing

When building automation that combines 3+ systems or APIs, test the hardest integration point in isolation before writing the full script.

## Rule

Multi-system integrations fail at boundaries — not in any single system but at the seam between them. A 10-line spike that tests the hardest seam saves hours debugging a full script that can't work.

## Procedure

1. **Identify the single hardest unknown.** Ask: "Which integration point am I least confident about?"
2. **Write a 10-line throwaway test** for just that one thing.
3. **Run it.** If it fails, you've saved hours. If it passes, proceed.

## Cost Analysis

| | Skip spike | Do spike |
|---|---|---|
| Spike succeeds | N/A | +5 min |
| Spike fails | Hours debugging dead-end script | 5 min + pivot |
| Expected value | Negative (boundary failures are the norm) | Positive |

## Common Integration Unknowns

- Can API A connect to service B in this environment/process?
- Does state persist across the lifecycle event I'm relying on?
- Will the runtime I'm using support the protocol I'm assuming?

## Anti-Pattern

Building the full 400-line script first, then discovering the core integration is impossible — requiring full restart after multiple debug iterations across unrelated failure modes.
61 changes: 61 additions & 0 deletions domains/coding/knowledge/type-level-assertions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
name: type-level-assertions
domain: coding
description: Compile-time type tests using branded uninhabitable types, IsEquivalent guards against any, and mutual-assignability checks.
---

# Type-Level Compile-Time Assertions

Pattern for writing type-level tests that produce compiler errors on failure rather than runtime assertions.

## Core Types

```typescript
type _ = { readonly _: unique symbol };

type IsAny<T> = 0 extends 1 & T ? true : false;
type IsNever<T> = [T] extends [never] ? true : false;

type IsEquivalent<A, B> =
IsAny<A> extends true ? IsAny<B>
: IsAny<B> extends true ? false
: [A, B] extends [B, A] ? true : false;

type Expect<
X extends [X, V] extends [V, X] ? V : V & _,
V = true,
> = IsNever<V> extends true ? X
: IsNever<X> extends true ? Expect<X, V>
: X;
```

## Usage

Organize assertions in named tuple types. Each element either compiles or produces a type error at the failing assertion.

```typescript
type Describe_MyFeature = [
Expect<IsEquivalent<Actual, Expected>>,
Expect<IsEquivalent<EdgeCase, EdgeExpected>, false>,
];
```

## Design Decisions

| Decision | Rationale |
|----------|-----------|
| `_` branded uninhabitable type | `V & _` is unsatisfiable, forcing a type error when `X` doesn't match `V` |
| `IsEquivalent` guards against `any` | Naive `[A, B] extends [B, A]` returns `true` if either side is `any` |
| `IsNever` wraps in tuple | Prevents distributive conditional evaluation over `never` |
| `Expect` uses naive `& _` constraint, not `IsEquivalent` | Referencing `IsEquivalent` in `Expect`'s own bound creates a circular constraint (TS2313) |

## Known Limitation

`Expect<any, string>` passes silently. Use `Expect<IsEquivalent<any, string>>` when `any`-safety matters.

## ESLint Configuration

Type-level spec files (`*.spec.ts`) need relaxed rules:
- Disable `no-unused-vars` (tuple types are never "used")
- Allow `Describe_` prefixed type names
- Exclude E2E test files from these overrides
36 changes: 36 additions & 0 deletions domains/coding/knowledge/typescript-intermediate-defaults.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
name: typescript-intermediate-defaults
domain: coding
description: Break complex conditional types into a chain of named intermediate defaulted type parameters instead of nesting conditionals.
---

# Intermediate Defaulted Type Parameters

When a conditional type needs to resolve multiple aspects of a generic input, chain intermediate defaulted parameters instead of nesting conditionals.

## Pattern

```typescript
type InferResult<
Input extends Record<PropertyKey, unknown>,
Step1 = Input extends { key: infer V } ? V : never,
Step2 = [Step1] extends [never] ? false : true,
Step3 = Step1 extends { nested: infer V } ? V : never,
Result = Step2 extends true ? Step1 : Fallback,
> = Result;
```

## Why

| Benefit | Detail |
|---------|--------|
| Transparent resolution chain | Each step is named and hoverable in IDE |
| Flat debugging | Hover any parameter to see its resolved value without tracing nested branches |
| Extensible | Adding resolution steps doesn't increase nesting depth |
| Compatible with `import()` inference | Works where deferred conditional evaluation in generics prevents resolution |

## When To Use

- Complex conditional types with 3+ resolution steps
- Types handling multiple structural patterns (default exports, named exports, nested namespaces)
- When deferred conditional evaluation blocks resolution in generic type parameters
51 changes: 51 additions & 0 deletions domains/coding/knowledge/typescript-strict-flag-pattern.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
name: typescript-strict-flag-pattern
domain: coding
description: Fold a wrapper type's stricter constraint into the base type behind a Strict boolean flag parameter instead of duplicating branching logic.
---

# Strict-Flag DRY Pattern

When a wrapper type duplicates branching logic from a base type to add a stricter constraint, fold the constraint into the base type behind a boolean flag.

## Before (duplicated logic)

```typescript
type InferComponent<Module, ...> = /* branch on condition */;

type StrictComponent<Module, ...> =
ConditionA extends true
? InferComponent<Module>
: ConditionB extends true
? InferComponent<Module>
: never;
```

## After (consolidated)

```typescript
type InferComponent<
Module,
Strict extends boolean = false,
FromNamedExport = Strict extends true
? ConditionB extends true
? ValidResult
: never
: ValidResult,
> = /* single branch on primary condition */;

type StrictComponent<Module> = InferComponent<Module, true>;
```

## When To Apply

- Wrapper type delegates to base type in all passing branches
- Wrapper only adds a `never` gate for certain input shapes
- `Strict = false` default preserves backward compatibility for existing callers

## Common Pitfalls

| Mistake | Correct Approach |
|---------|-----------------|
| Keeping the wrapper type after consolidation | Delete it — callers use `Base<Input, true>` directly |
| Setting `Strict = true` as the default | Breaks callers not expecting the stricter behavior |
Empty file added domains/coding/skills/.gitkeep
Empty file.
61 changes: 61 additions & 0 deletions domains/coding/skills/simulator-control/repos/metamask-mobile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
repo: metamask-mobile
parent: simulator-control
---

# Simulator Control — MetaMask Mobile

`agent-device` is installed as a local dependency. All device commands run via `yarn agent-device <command>` in the shell.

## Step 1 — Version Check

```bash
yarn agent-device --version
```

Require `version >= 0.14.0`. If the version is lower, ask the user to bump the `agent-device` dependency in `package.json`.

## Step 2 — Prerequisites

Before any agent-device command:

1. Confirm Metro is running by checking the terminals folder for an active `yarn watch:clean` process. Do NOT start Metro yourself — if it is not running, stop and ask the user to run `yarn watch:clean` in a separate terminal, then wait for confirmation.
2. Confirm a simulator is booted:
```bash
yarn agent-device devices --platform ios
```
If none are booted, ask the user to boot one via Xcode Simulator, then wait for confirmation.

## App Identifiers

| Platform | App identifier |
|----------|-----------------------------|
| iOS | `io.metamask.MetaMask` |
| Android | `io.metamask` |

```bash
yarn agent-device open io.metamask.MetaMask --platform ios
yarn agent-device open io.metamask --platform android
```

## Core Loop

```bash
yarn agent-device devices --platform ios
yarn agent-device open io.metamask.MetaMask --platform ios --device "iPhone 17"
yarn agent-device snapshot -i
yarn agent-device screenshot
```

Run `yarn agent-device --help` for the full list of interaction, validation, and evidence commands.

## Deep Link Navigation

To navigate directly to a screen, use the `metamask://` URL scheme instead of tapping through the UI:

```bash
yarn agent-device open "metamask://<route>" --platform ios
yarn agent-device open "metamask://<route>" --platform android
```

Check `app/core/AppConstants.js` and deeplink handler files for available routes.
11 changes: 11 additions & 0 deletions domains/coding/skills/simulator-control/skill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
name: simulator-control
description: Controls iOS/Android simulators to verify UI changes. Use when opening the app, navigating to a screen, taking a screenshot, checking what the UI looks like, tapping/typing/scrolling, verifying an implementation on device, or collecting visual evidence for a PR.
maturity: experimental
---

# Simulator Control

This skill only applies to MetaMask Mobile.

If `agent-device` is not listed in the project's `package.json`, stop and tell the user this skill is not supported for their project.
46 changes: 46 additions & 0 deletions domains/coding/skills/ts2589-workaround/skill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
maturity: experimental
name: ts2589-workaround
description: Fix TS2589 "type instantiation is excessively deep" in Expect<> constraints by pre-resolving complex conditionals to boolean before the constraint sees them.
---

# TS2589 Workaround: Pre-Resolve Before Expect

## When To Use

- A test file hits TS2589 on `Expect<SomeComplexType, Expected>`
- The failing type involves union distribution, `infer` chains, or multi-step conditionals
- `Expect<ComplexConditional>` fails even though the type itself resolves correctly

## Do Not Use When

- TS2589 is in production code, not test assertions — fix the type itself, don't work around it
- The type is simple enough that TypeScript resolves it without hitting the depth limit

## Workflow

1. Extract the complex type to a standalone alias
2. Manually flatten the equivalence check to `true`/`false` using an extends-pair
3. Feed the boolean result to `Expect<>`, not the original type

```typescript
// 1. Extract to alias
type Resolved = ComplexConditional<Input, true>;

// 2. Flatten to boolean
type Check = [Resolved] extends [Expected]
? [Expected] extends [Resolved]
? true
: false
: false;

// 3. Feed boolean to Expect
Expect<Check>
```

## Common Pitfalls

| Mistake | Correct Approach |
|---------|-----------------|
| Using `type Resolved = ComplexType` then `Expect<Resolved, Expected>` | TypeScript does not eagerly resolve aliases before constraint-checking — it re-expands inline. Use the manual extends-pair flatten. |
| Wrapping in a utility type | Any utility that re-introduces a conditional constraint re-triggers depth expansion. Flatten to `true`/`false` directly. |
Loading
Loading