AgentSkillsCN

stack-patterns

参考Outfitter Stack的相关模式,包括Result类型、处理器契约、错误分类体系,以及@outfitter/*系列包的使用规范。当您学习技术栈、查阅相关模式、深入了解各个包,或在提及“Result”“处理器”“错误分类体系”“OutfitterError”“CLI输出”“分页”“MCP服务器”“MCP工具”“结构化日志”“敏感信息脱敏”“测试处理器”“守护进程”“IPC”或“@outfitter/*”时,可随时查阅并加以应用。

SKILL.md
--- frontmatter
name: stack-patterns
version: 0.1.0
description: Reference for Outfitter Stack patterns including Result types, Handler contract, Error taxonomy, and @outfitter/* package conventions. Use when learning the stack, looking up patterns, understanding packages, or when "Result", "Handler", "error taxonomy", "OutfitterError", "CLI output", "pagination", "MCP server", "MCP tool", "structured logging", "redaction", "test handler", "daemon", "IPC", or "@outfitter/*" are mentioned.
allowed-tools: Read Grep Glob

Outfitter Stack Patterns

Primary reference for @outfitter/* package conventions.

Handler Contract

Handlers are pure functions that:

  • Accept typed input and context
  • Return Result<TOutput, TError>
  • Know nothing about transport (CLI flags, HTTP headers, MCP tool schemas)
typescript
type Handler<TInput, TOutput, TError extends OutfitterError> = (
  input: TInput,
  ctx: HandlerContext
) => Promise<Result<TOutput, TError>>;

Example

typescript
import { Result, NotFoundError, type Handler } from "@outfitter/contracts";

export const getUser: Handler<{ id: string }, User, NotFoundError> = async (input, ctx) => {
  ctx.logger.debug("Fetching user", { userId: input.id });
  const user = await db.users.findById(input.id);

  if (!user) {
    return Result.err(new NotFoundError("user", input.id));
  }
  return Result.ok(user);
};

Why? Testability (just call the function), reusability (same handler for CLI/MCP/HTTP), type safety (explicit types), composability (handlers wrap handlers).

Result Types

Uses Result<T, E> from better-result for explicit error handling.

typescript
import { Result } from "@outfitter/contracts";

// Create
const ok = Result.ok({ name: "Alice" });
const err = Result.err(new NotFoundError("user", "123"));

// Check
if (result.isOk()) {
  console.log(result.value);  // TypeScript knows T
} else {
  console.log(result.error);  // TypeScript knows E
}

// Pattern match
const message = result.match({
  ok: (user) => `Found ${user.name}`,
  err: (error) => `Error: ${error.message}`,
});

// Combine
const combined = combine2(result1, result2);  // tuple or first error

Error Taxonomy

Ten categories map to exit codes and HTTP status:

CategoryExitHTTPWhen to Use
validation1400Invalid input, schema failures
not_found2404Resource doesn't exist
conflict3409Already exists, version mismatch
permission4403Forbidden action
timeout5504Operation took too long
rate_limit6429Too many requests
network7503Connection failures
internal8500Unexpected errors, bugs
auth9401Authentication required
cancelled130499User interrupted (Ctrl+C)
typescript
import { ValidationError, NotFoundError, getExitCode } from "@outfitter/contracts";

new ValidationError("Invalid email", { field: "email" });
new NotFoundError("user", "user-123");

getExitCode(error.category);   // 2 for not_found
getStatusCode(error.category); // 404 for not_found

Validation

Use Zod with createValidator for type-safe validation returning Results:

typescript
import { createValidator } from "@outfitter/contracts";
import { z } from "zod";

const InputSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
});

const validateInput = createValidator(InputSchema);

// In handler
const inputResult = validateInput(rawInput);
if (inputResult.isErr()) return inputResult;
const input = inputResult.value;  // typed as z.infer<typeof InputSchema>

Context

HandlerContext carries cross-cutting concerns:

typescript
import { createContext } from "@outfitter/contracts";

const ctx = createContext({
  logger: myLogger,           // structured logger
  config: resolvedConfig,     // merged configuration
  signal: controller.signal,  // cancellation
  workspaceRoot: "/project",
});

// ctx.requestId is auto-generated UUIDv7 for tracing
FieldTypeDescription
requestIdstringAuto-generated UUIDv7
loggerLoggerStructured logger
configResolvedConfigMerged config
signalAbortSignalCancellation signal
workspaceRootstringProject root
cwdstringCurrent directory

Package Reference

PackagePurposeWhen to Use
@outfitter/contractsResult types, errors, Handler contractAlways (foundation)
@outfitter/typesType utilities, collection helpersType manipulation
@outfitter/cliCLI commands, output modes, formattingCLI applications
@outfitter/mcpMCP server, tool registration, Zod schemasAI agent tools
@outfitter/configXDG paths, config loading, env handlingConfiguration needed
@outfitter/loggingStructured logging, sinks, redactionLogging needed
@outfitter/daemonBackground services, IPC, health checksLong-running services
@outfitter/file-opsSecure paths, atomic writes, file lockingFile operations
@outfitter/statePagination, cursor statePaginated data
@outfitter/testingTest harnesses, fixtures, Bun testTesting

Selection guidance:

  • All projects start with @outfitter/contracts
  • CLI apps add @outfitter/cli (includes UI components)
  • MCP servers add @outfitter/mcp
  • Projects with config add @outfitter/config
  • File operations need @outfitter/file-ops for safety

Type Utilities

@outfitter/types provides collection helpers and type utilities:

Collection Helpers

typescript
import { sortBy, dedupe, chunk } from "@outfitter/types";

// Sort by property
const users = [{ name: "Bob" }, { name: "Alice" }];
sortBy(users, "name");         // [{ name: "Alice" }, { name: "Bob" }]
sortBy(users, u => u.name);    // Same, with accessor function

// Remove duplicates
dedupe([1, 2, 2, 3, 3, 3]);    // [1, 2, 3]
dedupe(users, u => u.name);    // Dedupe by property

// Split into chunks
chunk([1, 2, 3, 4, 5], 2);     // [[1, 2], [3, 4], [5]]

Type Utilities

Standard TypeScript utility types for common patterns:

typescript
import type { Prettify, DeepPartial, Nullable } from "@outfitter/types";

// Prettify: Flatten complex intersection types for better IntelliSense
type Combined = { a: string } & { b: number };
type Pretty = Prettify<Combined>;  // Shows { a: string; b: number }

// DeepPartial: Make all properties optional recursively
type Config = { db: { host: string; port: number } };
type PartialConfig = DeepPartial<Config>;

// Nullable: T | null
type MaybeUser = Nullable<User>;

Domain Error Mapping

Map your domain errors to the 10 taxonomy categories:

Domain ErrorStack CategoryError ClassExitHTTP
Not foundnot_foundNotFoundError2404
Invalid inputvalidationValidationError1400
Already existsconflictConflictError3409
No permissionpermissionPermissionError4403
Auth requiredauthAuthError9401
Timed outtimeoutTimeoutError5504
Connection failednetworkNetworkError7503
Limit exceededrate_limitRateLimitError6429
Bug/unexpectedinternalInternalError8500
User cancelledcancelledCancelledError130499

Mapping examples:

typescript
// "User not found" -> NotFoundError
new NotFoundError("user", userId);

// "Invalid email format" -> ValidationError
new ValidationError("Invalid email", { field: "email" });

// "User already exists" -> ConflictError
new ConflictError("Email already registered", { email });

// "Cannot delete admin" -> PermissionError
new PermissionError("Cannot delete admin users");

// Unexpected errors -> InternalError
new InternalError("Database connection failed", { cause: error });

Bun-First APIs

Prefer Bun-native APIs:

NeedBun API
HashingBun.hash()
GlobbingBun.Glob
SemverBun.semver
ShellBun.$
ColorsBun.color()
String widthBun.stringWidth()
SQLitebun:sqlite
UUID v7Bun.randomUUIDv7()

References

Core Patterns

Package Deep Dives