Install
$ agentstack add skill-mateonunez-skills-types-first-guards ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Types First Guards
> "If the types don't catch the error, the types are lying."
Philosophy
I use TypeScript as a guardrail against the entire class of "oops, I passed the wrong ID" bugs. The type system should make certain mistakes impossible at compile time, not catch them at runtime.
The Pattern
Nominal domain types for anything that should never be confused at the type level, even if runtime representation is identical:
type UserId = string & { readonly __brand: 'UserId' };
const userId = (id: string): UserId => id as UserId;
type OrgId = string & { readonly __brand: 'OrgId' };
const orgId = (id: string): OrgId => id as OrgId;
// Now this type-checks:
const user = await fetchUser(userId('42'));
const org = await fetchOrg(orgId('42'));
// But this does NOT:
const badUser = await fetchUser(orgId('42')); // ✗ Type error
No reflexive runtime checks. The type system prevents the mistake in the first place.
Discriminated Unions for Multi-State Data
For domain concepts that have distinct states or variants, use __type discriminator unions:
type Order =
| { __type: 'pending'; id: OrderId; items: OrderItem[] }
| { __type: 'confirmed'; id: OrderId; items: OrderItem[]; confirmedAt: Date }
| { __type: 'shipped'; id: OrderId; items: OrderItem[]; trackingNumber: string }
| { __type: 'cancelled'; id: OrderId; reason: string };
function handleOrder(order: Order) {
switch (order.__type) {
case 'pending': return processPayment(order);
case 'confirmed': return scheduleShipment(order);
case 'shipped': return notifyCustomer(order);
case 'cancelled': return refund(order);
}
// ✓ TypeScript enforces exhaustiveness
}
The compiler forces you to handle every case. Refactoring adds a state? The compiler catches you.
When Nominal Types Help Most
- IDs that should never be mixed: UserId, OrgId, ProjectId, OrderId. Use this pattern aggressively.
- Concepts from your domain glossary (in CONTEXT.md) that have different meanings even if they're structurally the same string/number.
- Auth boundaries: SessionToken vs RefreshToken vs ApiKey — different tokens, different lifespans, different threat models.
Do NOT do this for every string. Use it for semantic differences, not syntactic ones.
Anti-Patterns
unknownin business logic. Unknown is "I don't know the type." If you don't know, you're missing domain understanding. Dig until you do.as anyto make TypeScript shut up. This is a sign the types don't reflect reality. Fix the types, not the code.- Discriminated unions without exhaustiveness checking. If you write a switch on
__typewithout handling all cases, the type system should reject it. Ensure you're usingneverin unreachable branches to force exhaustiveness.
function handleOrder(order: Order) {
switch (order.__type) {
case 'pending': return processPayment(order);
case 'confirmed': return scheduleShipment(order);
// ✗ Missing 'shipped' and 'cancelled' cases
default: return never; // Type error: can't assign Order to never
}
}
- Mixing domain and primitive types. Never do
const userId: stringwhen you have a UserId type. Always use the nominal type. - Type assertions on untrusted data at boundaries.
JSON.parse(userInput) as Orderis a lie. Validate first, then type-assert.
Phases
- Identify domain concepts — what are the distinct semantic types in your domain? (from CONTEXT.md if it exists)
- Create nominal types or discriminated unions for each
- Enforce at boundaries — HTTP handlers, DB queries, external API calls. Validate untrusted data, then assert types.
- Use in business logic — pass these types through your service layer so mistakes are caught early
- Verify exhaustiveness — switch statements on discriminated unions should have type-checker enforcement via
neverin unreachable code
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: mateonunez
- Source: mateonunez/skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.