AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Types And Interfaces

skill-techymt-claude-code-superpowers-types-and-interfaces · by TechyMT

|

No reviews yet
0 installs
6 views
0.0% view→install

Install

$ agentstack add skill-techymt-claude-code-superpowers-types-and-interfaces

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No 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.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-techymt-claude-code-superpowers-types-and-interfaces)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
4mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

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 →
Are you the author of Types And Interfaces? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Types and Interfaces

The pattern

Claude Code uses three type patterns consistently: discriminated unions for output data shapes (the Zod outputSchema for tools that can return one of several kinds of result), Zod schemas as single source of truth for tool inputs (the schema drives runtime validation AND TypeScript types simultaneously via z.infer<>), and DeepImmutable wrappers for AppState to prevent accidental mutation.

The discipline is: define shape once, derive everything else. A Zod schema for a tool's input is defined once in inputSchema. The TypeScript type z.infer is derived from it. The LLM's JSON schema is derived from it. Runtime validation uses it. There is no second interface FooInput to drift from the schema.

Why this matters

The LLM produces JSON. JSON must be validated at runtime. TypeScript types catch compile-time issues. If these are defined separately, they drift: the TypeScript interface says limit?: number but the runtime validator doesn't check it's a number, leading to a crash at runtime. Zod solves this by being both the runtime validator and the source for z.infer<> types.

Discriminated unions for output data types (e.g. a file read that returns text, image, pdf, or unchanged) make exhaustive handling compiler-enforced. The TypeScript compiler narrows the type in each case branch. This eliminates an entire class of missing-case bugs. For errors, tools throw rather than returning an error variant — the success/error boundary is the thrown-exception contract, not the ToolResult shape.

AppState uses DeepImmutable to prevent anyone from mutating state directly. All state changes must go through setAppState(prev => next), making state transitions traceable and atomic.

How to apply it

  1. Tool inputs: always use Zod. Use lazySchema() wrapper from utils/lazySchema.ts to defer parsing until first access (faster startup). Use z.strictObject (not z.object) to reject unknown keys. Derive the TypeScript type with type MyInput = z.infer.
  2. Tool output shapes: when a tool can return one of several kinds of result, define a Zod outputSchema using z.discriminatedUnion. Derive the TypeScript type with type Output = z.infer.
  3. AppState fields: add to the AppState type in AppStateStore.ts. The type is DeepImmutable — nested objects are also deeply immutable. You cannot .push() to arrays; you must spread them.
  4. Message types: use the existing discriminated union. Don't add new message types without understanding the normalization pipeline — messages flow through multiple transformations before reaching the API.
  5. Avoid any: use unknown for values whose type is genuinely unknown at the call site, then narrow with type guards.

In the source

// Source: src/Tool.ts (ToolResult — tools return data or throw, no error variant)
export type ToolResult = {
  data: T
  newMessages?: (UserMessage | AssistantMessage | AttachmentMessage | SystemMessage)[]
  contextModifier?: (context: ToolUseContext) => ToolUseContext
  mcpMeta?: { _meta?: Record; structuredContent?: Record }
}

// Tools signal errors by throwing — not by returning { type: 'error' }.
// A successful call returns { data: result }:
return { data: result }

// Source: src/tools/FileReadTool/FileReadTool.ts (discriminated union for output data)
import { lazySchema } from '../../utils/lazySchema.js'
import { z } from 'zod'

// outputSchema uses discriminatedUnion because a file read can yield several kinds of result
const outputSchema = lazySchema(() =>
  z.discriminatedUnion('type', [
    z.object({
      type: z.literal('text'),
      file: z.object({ filePath: z.string(), content: z.string(), numLines: z.number() }),
    }),
    z.object({
      type: z.literal('image'),
      file: z.object({ filePath: z.string(), /* ... */ }),
    }),
    z.object({
      type: z.literal('pdf'),
      file: z.object({ filePath: z.string(), /* ... */ }),
    }),
    z.object({
      type: z.literal('file_unchanged'),
      file: z.object({ filePath: z.string() }),
    }),
  ])
)
type OutputSchema = ReturnType
export type Output = z.infer

// TypeScript narrows in each branch — no cast needed:
if (output.type === 'text') {
  console.log(output.file.content)  // TypeScript knows content: string here
}

// Source: src/tools/FileReadTool/FileReadTool.ts (Zod inputSchema as single source of truth)
// lazySchema() defers parsing until first access — not evaluated at module load
const inputSchema = lazySchema(() =>
  z.strictObject({  // strictObject rejects unknown keys
    file_path: z.string().describe('The absolute path to the file to read'),
    offset: z.number().int().nonnegative().optional()
      .describe('Line number to start reading from'),
    limit: z.number().int().positive().optional()
      .describe('Maximum number of lines to read'),
  })
)
type InputSchema = ReturnType
export type Input = z.infer
// Equivalent to: { file_path: string; offset?: number; limit?: number }

// Source: src/state/AppStateStore.ts (DeepImmutable AppState)
export type AppState = DeepImmutable  // DeepImmutable makes this read-only recursively
  // ...
}>

// Mutation is impossible:
// state.verbose = true  // TypeScript error: cannot assign to read-only property

// Correct: derive new state
context.setAppState(prev => ({ ...prev, verbose: true }))

// Source: src/Task.ts (TaskStatus string literal union)
export type TaskStatus = 'pending' | 'running' | 'completed' | 'failed' | 'killed'

// Exhaustive check in switch is enforced by TypeScript:
function handleStatus(status: TaskStatus) {
  switch (status) {
    case 'pending': return 'pending'
    case 'running': return 'running'
    case 'completed': return 'completed'
    case 'failed': return 'failed'
    case 'killed': return 'killed'
    // No default needed — TypeScript knows all cases are covered
  }
}

The lazySchema() wrapper deserves attention: Zod schema construction does non-trivial work at parse time (it builds validator functions). Calling it at module load time adds to startup latency. lazySchema() wraps the constructor in a thunk evaluated only on first access, which is typically when the first tool call happens — after startup is already complete.

Apply it to your code

Before — separate interface and runtime validation that can drift:

// TypeScript type defined separately from runtime validator
interface SearchInput {
  query: string
  maxResults?: number
  caseSensitive?: boolean
}

// Runtime validator defined separately — can drift from the interface above
function validateSearchInput(input: unknown): SearchInput {
  if (typeof input !== 'object') throw new Error('Not an object')
  if (typeof (input as any).query !== 'string') throw new Error('query must be string')
  return input as SearchInput  // Dangerous cast
}

After — Zod as single source of truth:

import { lazySchema } from '../../utils/lazySchema.js'
import { z } from 'zod'

// One definition drives both TypeScript types and runtime validation
const inputSchema = lazySchema(() =>
  z.strictObject({  // rejects unknown keys — use z.strictObject, not z.object
    query: z.string().min(1).describe('Text to search for'),
    maxResults: z.number().int().positive().max(1000).optional()
      .describe('Maximum number of results (default: 100)'),
    caseSensitive: z.boolean().optional()
      .describe('Whether to match case exactly (default: false)'),
  })
)

// Type derived from schema — no separate interface to maintain
type InputSchema = ReturnType
type SearchInput = z.infer
// { query: string; maxResults?: number; caseSensitive?: boolean }

// The schema validates AND types the result — no cast needed
const parsed = inputSchema.parse(rawInput)  // throws ZodError if invalid
const safe = inputSchema.safeParse(rawInput)  // returns { success, data } or { success: false, error }

Signals that you need this pattern

  • A TypeScript interface and a manual validation function exist side-by-side for the same data shape
  • A tool that returns multiple kinds of result has no outputSchema with a discriminated union
  • AppState is mutated with direct assignment (state.field = value) instead of via setAppState
  • Tool input types are defined as plain TypeScript interfaces rather than derived from Zod schemas
  • as any or unsafe type casts appear where Zod validation should be used

Signals that you're over-applying it

  • Internal pure-function parameters that are always called with known types don't need Zod validation
  • Simple string literal union types ('left' | 'right') don't need Zod schemas — just use the union directly
  • Don't wrap every internal helper's return type in a discriminated union; use discriminated unions at system/module boundaries

Works with

  • tool-definition — Zod schemas as inputSchema and outputSchema in the Tool interface
  • error-handling — tools throw for errors; ToolResult carries success data only
  • domain-model — AppState's DeepImmutable type shape

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.