Token导航 LogoToken导航

Agent Skill · 前端设计

function-composition---building-from-small-pieces(由小块组成的功能组合)

function-composition---building-from-small-pieces 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

function-composition---building-from-small-pieces 是前端设计类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证信息还不明确,暂无第三方扫描结果,按安装量在前端设计类的 10,216 个 Skill 里排第 692。

总安装 14,056周安装 588GitHub Stars 6下载 4,077核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
unknown

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:function-composition---building-from-small-pieces(由小块组成的功能组合)
来源仓库:https://github.com/whatiskadudoing/fp-ts-skills
仓库路径:skills/function-composition---building-from-small-pieces
安装命令:
npx skills add https://github.com/whatiskadudoing/fp-ts-skills --skill 'Function Composition - Building from Small Pieces'
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/whatiskadudoing/fp-ts-skills --skill 'Function Composition - Building from Small Pieces'

这个 Skill 是做什么的

前端设计类 Skill 关注页面结构、视觉规范和组件写法,输出通常是可运行的界面代码。

用于指导从简单函数片段构建复杂行为的编程实践,强调管道式数据处理。推荐使用 data-last 风格提升组合灵活性,并借助日志跟踪调试而不破坏流程。目标是写出更清晰易维护的代码,仅在必要时使用组合而非强制套用模式。

适用于 Codex、Claude、Cursor、Gemini CLI 等宿主环境,需通过 npx 命令安装使用。安装前建议确认宿主兼容性,避免因语法差异导致函数组合失效或运行时错误。

来源仓库内路径:skills/function-composition---building-from-small-pieces

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/whatiskadudoing/fp-ts-skills --skill 'Function…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

function-composition---building-from-small-pieces 怎么安装?

在终端执行 npx skills add https://github.com/whatiskadudoing/fp-ts-skills --skill 'Function Compositi…。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

function-composition---building-from-small-pieces 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

function-composition---building-from-small-pieces 需要什么权限?

权限级别是需要联网。该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

function-composition---building-from-small-pieces 是免费的吗?

公开资料没有标注明确许可证,收费和商用限制都无法确认。正式使用前请到来源仓库核对授权说明。

function-composition---building-from-small-pieces 用的人多吗?

公开数据是累计安装 14,056 次,近一周安装 588 次,下载 4,077 次,GitHub 6 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

function-composition---building-from-small-pieces 安全吗?

没有查到第三方扫描结果,安全性无法从公开资料判断,安装前请自行审查源码、依赖和命令行为。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。

先想清楚的地方

  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 没有第三方扫描结果,安全性只能靠自己读源码判断。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理前端设计类任务、又不想每次都手写提示词的人。

不太适合

  • 需要明确授权才能用到公司项目里的场景。
  • 要求有安全扫描结论才肯安装的团队。
  • 需要厂商级技术支持和 SLA 的生产系统。

前端设计类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
frontend-design

前端设计 · Codex、Claude

8,823,360126,072只读MIT通过
vercel-composition-patterns

Vercel 组合模式 · Codex、Claude

3,858,81625,893需要联网MIT通过
vercel-react-native-skills

Vercel React native skills 前端 · Codex、Claude

2,468,66425,878只读MIT通过
adapt

adapt 搜索 · Codex、Claude

2,027,04023,125操作浏览器未明确通过
bolder

bolder 分析 · Codex、Claude

2,004,28823,130只读未明确通过
function-composition---building-from-small-pieces

由小块组成的功能组合 · 本页

14,0566需要联网未明确未收录

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

Function Composition - Building from Small Pieces

The core idea is simple:

pipe(data, fn1, fn2, fn3) === fn3(fn2(fn1(data)))

That's it. No category theory needed. Just chain functions together, left to right.


1. pipe() - Your New Best Friend

The Basic Pattern

import { pipe } from 'fp-ts/function'

// Instead of nested calls:
const result = formatOutput(calculateTotal(validateInput(parseData(rawInput))))

// Write it as a pipeline:
const result = pipe(
  rawInput,
  parseData,
  validateInput,
  calculateTotal,
  formatOutput
)

Why This Matters

Before (nested calls):

// Read from inside out, right to left - confusing
const userName = capitalize(trim(getProperty('name')(user)))

After (pipe):

// Read top to bottom, left to right - natural
const userName = pipe(
  user,
  getProperty('name'),
  trim,
  capitalize
)

Real Example: Processing User Input

import { pipe } from 'fp-ts/function'

interface UserInput {
  email: string
  name: string
  age: string
}

interface CleanUser {
  email: string
  name: string
  age: number
}

// Small, focused functions
const trimEmail = (input: UserInput): UserInput => ({
  ...input,
  email: input.email.trim().toLowerCase()
})

const trimName = (input: UserInput): UserInput => ({
  ...input,
  name: input.name.trim()
})

const parseAge = (input: UserInput): CleanUser => ({
  ...input,
  age: parseInt(input.age, 10) || 0
})

// Compose them
const cleanUserInput = (raw: UserInput): CleanUser =>
  pipe(raw, trimEmail, trimName, parseAge)

// Use it
cleanUserInput({ email: '  ALICE@EMAIL.COM  ', name: '  Alice  ', age: '30' })
// { email: 'alice@email.com', name: 'Alice', age: 30 }

2. Building Reusable Utilities

The key is making small functions that do one thing well.

String Utilities

import { pipe, flow } from 'fp-ts/function'

// Basic building blocks
const trim = (s: string): string => s.trim()
const lowercase = (s: string): string => s.toLowerCase()
const uppercase = (s: string): string => s.toUpperCase()
const replace = (pattern: RegExp, replacement: string) =>
  (s: string): string => s.replace(pattern, replacement)
const prefix = (pre: string) => (s: string): string => `${pre}${s}`
const suffix = (suf: string) => (s: string): string => `${s}${suf}`

// Combine them into useful utilities
const slugify = flow(
  trim,
  lowercase,
  replace(/\s+/g, '-'),
  replace(/[^a-z0-9-]/g, '')
)

const titleCase = flow(
  trim,
  lowercase,
  replace(/\b\w/g, c => c.toUpperCase())
)

const kebabCase = flow(
  trim,
  replace(/([a-z])([A-Z])/g, '$1-$2'),
  lowercase,
  replace(/\s+/g, '-')
)

// Use them
slugify('  Hello World! ')     // 'hello-world'
titleCase('hello world')       // 'Hello World'
kebabCase('myVariableName')    // 'my-variable-name'

Number Utilities

const clamp = (min: number, max: number) =>
  (n: number): number => Math.max(min, Math.min(max, n))

const round = (decimals: number) =>
  (n: number): number => Math.round(n * 10 ** decimals) / 10 ** decimals

const multiply = (factor: number) =>
  (n: number): number => n * factor

const add = (amount: number) =>
  (n: number): number => n + amount

// Combine for specific use cases
const toPercentage = flow(
  multiply(100),
  round(1),
  suffix('%')
)

const formatPrice = flow(
  round(2),
  n => n.toFixed(2),
  prefix('$')
)

const normalizeScore = flow(
  clamp(0, 100),
  round(0)
)

// Use them
toPercentage(0.8567)  // '85.7%'
formatPrice(19.999)   // '$20.00'
normalizeScore(105)   // 100

Array Utilities

import * as A from 'fp-ts/Array'
import { pipe, flow } from 'fp-ts/function'

// Property accessors
const prop = <T, K extends keyof T>(key: K) =>
  (obj: T): T[K] => obj[key]

// Predicates
const isNotNull = <T>(value: T | null | undefined): value is T =>
  value != null

const hasLength = (min: number) =>
  (arr: readonly unknown[]): boolean => arr.length >= min

// Combining filters and maps
interface Product {
  id: string
  name: string
  price: number
  inStock: boolean
}

const getInStockProductNames = flow(
  A.filter((p: Product) => p.inStock),
  A.map(prop('name'))
)

const getTotalValue = flow(
  A.map((p: Product) => p.price),
  A.reduce(0, (acc, price) => acc + price)
)

const getProductsSortedByPrice = flow(
  A.sort<Product>((a, b) => a.price - b.price)
)

3. Data-Last for Flexibility

Why Argument Order Matters

// Data-first: Hard to compose
const map1 = <A, B>(arr: A[], fn: (a: A) => B): B[] => arr.map(fn)

// Can't easily create reusable functions:
const doubleAll = (arr: number[]) => map1(arr, n => n * 2)  // Must wrap

// Data-last: Easy to compose
const map2 = <A, B>(fn: (a: A) => B) => (arr: A[]): B[] => arr.map(fn)

// Create reusable functions by partial application:
const doubleAll = map2((n: number) => n * 2)

// Works naturally in pipes:
pipe([1, 2, 3], doubleAll)  // [2, 4, 6]

The Pattern

// General rule: configuration first, data last

// Good: Configuration -> Data
const filter = <A>(predicate: (a: A) => boolean) =>
  (arr: A[]): A[] => arr.filter(predicate)

const map = <A, B>(fn: (a: A) => B) =>
  (arr: A[]): B[] => arr.map(fn)

const formatWith = (formatter: Intl.NumberFormat) =>
  (n: number): string => formatter.format(n)

// All work smoothly in pipes:
const processNumbers = flow(
  filter((n: number) => n > 0),
  map(n => n * 2),
  formatWith(new Intl.NumberFormat('en-US'))
)

Converting Data-First APIs

// Many built-in methods are data-first (method on object)
// Wrap them to be data-last

// Date formatting
const formatDate = (options: Intl.DateTimeFormatOptions) =>
  (locale: string) =>
    (date: Date): string => date.toLocaleDateString(locale, options)

const formatShortDate = formatDate({ month: 'short', day: 'numeric' })('en-US')

pipe(new Date(), formatShortDate)  // 'Jan 30'

// JSON operations
const parseJSON = <T>() =>
  (str: string): T => JSON.parse(str)

const stringifyJSON = (indent?: number) =>
  <T>(data: T): string => JSON.stringify(data, null, indent)

// Regular expressions
const match = (regex: RegExp) =>
  (str: string): RegExpMatchArray | null => str.match(regex)

const test = (regex: RegExp) =>
  (str: string): boolean => regex.test(str)

const split = (separator: string | RegExp) =>
  (str: string): string[] => str.split(separator)

4. When Composition Helps (And When It Doesn't)

Good Uses for Composition

1. Multi-step data transformations:

// Processing API responses
const processApiResponse = flow(
  extractData,
  normalizeFields,
  validateSchema,
  transformForUI
)

2. Building specialized functions:

// Currency formatters from a general formatter
const formatCurrency = (locale: string, currency: string) =>
  (amount: number): string =>
    new Intl.NumberFormat(locale, { style: 'currency', currency }).format(amount)

const formatUSD = formatCurrency('en-US', 'USD')
const formatEUR = formatCurrency('de-DE', 'EUR')
const formatGBP = formatCurrency('en-GB', 'GBP')

3. Validation chains:

const validateEmail = flow(
  trim,
  lowercase,
  (email: string) => email.includes('@') ? email : null
)

const validateUsername = flow(
  trim,
  (name: string) => name.length >= 3 ? name : null
)

4. Event handlers:

const handleFormSubmit = flow(
  preventDefault,
  extractFormData,
  validateForm,
  submitToAPI
)

When NOT to Compose

1. When a simple function is clearer:

// Overengineered:
const isAdult = flow(
  prop<Person, 'age'>('age'),
  gte(18)
)

// Just write it:
const isAdult = (person: Person): boolean => person.age >= 18

2. When you need multiple inputs:

// Awkward with composition:
const calculateDiscount = (price: number, discountPercent: number): number =>
  pipe(
    price,
    multiply(1 - discountPercent / 100)
  )
// The discountPercent is awkwardly captured

// Just use a regular function:
const calculateDiscount = (price: number, discountPercent: number): number =>
  price * (1 - discountPercent / 100)

3. When debugging becomes hard:

// If you can't figure out what's happening:
const mysteryPipeline = flow(fn1, fn2, fn3, fn4, fn5, fn6, fn7)

// Break it up and name the stages:
const parse = flow(fn1, fn2)
const validate = flow(fn3, fn4)
const transform = flow(fn5, fn6, fn7)

// Or just use a regular function with intermediate variables:
const processData = (input: Input): Output => {
  const parsed = parse(input)
  const validated = validate(parsed)
  const transformed = transform(validated)
  return transformed
}

4. When the team doesn't know the pattern:

// If your team isn't familiar with FP:
const result = pipe(
  data,
  A.filter(isActive),
  A.map(getName),
  A.sort(ordString.compare)
)

// Consider the more familiar version:
const result = data
  .filter(isActive)
  .map(getName)
  .sort((a, b) => a.localeCompare(b))

Decision Guide

SituationUse Composition?
Multi-step transformationYes
Building reusable utilitiesYes
Single operationNo
Multiple unrelated inputsNo
Complex branching logicMaybe not
Team unfamiliar with FPStart simple

5. Debugging Pipelines

The trace Function

// Simple debug helper
const trace = <A>(label: string) =>
  (value: A): A => {
    console.log(`${label}:`, value)
    return value
  }

// Use it in pipelines
const result = pipe(
  input,
  trace('input'),
  step1,
  trace('after step1'),
  step2,
  trace('after step2'),
  step3,
  trace('final')
)

Conditional Tracing

// Only trace in development
const traceIf = (enabled: boolean) =>
  <A>(label: string) =>
    (value: A): A => {
      if (enabled) console.log(`${label}:`, value)
      return value
    }

const debug = traceIf(process.env.NODE_ENV === 'development')

pipe(
  data,
  debug('input'),
  transform,
  debug('output')
)

Structured Logging

// More sophisticated tracing
interface TraceOptions {
  label: string
  transform?: (value: unknown) => unknown
  condition?: (value: unknown) => boolean
}

const traceWith = (options: TraceOptions) =>
  <A>(value: A): A => {
    if (!options.condition || options.condition(value)) {
      const output = options.transform ? options.transform(value) : value
      console.log(`[${options.label}]`, output)
    }
    return value
  }

// Use it
pipe(
  users,
  traceWith({ label: 'users', transform: arr => `count: ${arr.length}` }),
  A.filter(isActive),
  traceWith({ label: 'active', transform: arr => `count: ${arr.length}` })
)

Breakpoint Debugging

// Insert a breakpoint
const breakpoint = <A>(value: A): A => {
  debugger  // Execution pauses here
  return value
}

pipe(
  data,
  step1,
  breakpoint,  // Inspect value here
  step2
)

Type Checking Mid-Pipeline

// Verify types are what you expect
const assertType = <Expected>() =>
  <Actual extends Expected>(value: Actual): Actual => value

pipe(
  data,
  parseInput,
  assertType<{ name: string; age: number }>(),  // TypeScript error if wrong
  formatOutput
)

Practical Patterns

Pattern 1: Data Processing Pipeline

import { pipe, flow } from 'fp-ts/function'
import * as A from 'fp-ts/Array'
import * as O from 'fp-ts/Option'

interface RawRecord {
  id: string
  timestamp: string
  value: string
  status: string
}

interface ProcessedRecord {
  id: string
  date: Date
  value: number
  isActive: boolean
}

// Individual processing steps
const parseTimestamp = (r: RawRecord) => ({
  ...r,
  date: new Date(r.timestamp)
})

const parseValue = (r: { id: string; date: Date; value: string; status: string }) => ({
  id: r.id,
  date: r.date,
  value: parseFloat(r.value) || 0,
  isActive: r.status === 'active'
})

const isValid = (r: ProcessedRecord): boolean =>
  !isNaN(r.date.getTime()) && r.value >= 0

const sortByDate = A.sort<ProcessedRecord>((a, b) =>
  a.date.getTime() - b.date.getTime()
)

// Compose into a pipeline
const processRecords = flow(
  A.map(parseTimestamp),
  A.map(parseValue),
  A.filter(isValid),
  sortByDate
)

// Use it
const result = processRecords(rawData)

Pattern 2: Creating Specialized Functions

// General HTTP client
interface RequestConfig {
  baseUrl: string
  headers: Record<string, string>
}

const createFetcher = (config: RequestConfig) =>
  (endpoint: string) =>
    <T>(): Promise<T> =>
      fetch(`${config.baseUrl}${endpoint}`, { headers: config.headers })
        .then(r => r.json())

// Create specialized fetchers
const apiConfig = {
  baseUrl: 'https://api.example.com',
  headers: { 'Authorization': 'Bearer token123' }
}

const apiFetch = createFetcher(apiConfig)

// Even more specialized
const fetchUsers = apiFetch('/users')<User[]>
const fetchProducts = apiFetch('/products')<Product[]>
const fetchOrders = apiFetch('/orders')<Order[]>

Pattern 3: Composing Validators

import * as E from 'fp-ts/Either'
import { pipe } from 'fp-ts/function'

type ValidationError = string
type Validator<T> = (value: T) => E.Either<ValidationError, T>

// Basic validators
const nonEmpty: Validator<string> = (s) =>
  s.length > 0
    ? E.right(s)
    : E.left('Value cannot be empty')

const minLength = (min: number): Validator<string> => (s) =>
  s.length >= min
    ? E.right(s)
    : E.left(`Must be at least ${min} characters`)

const maxLength = (max: number): Validator<string> => (s) =>
  s.length <= max
    ? E.right(s)
    : E.left(`Must be at most ${max} characters`)

const matches = (pattern: RegExp, message: string): Validator<string> => (s) =>
  pattern.test(s)
    ? E.right(s)
    : E.left(message)

// Compose validators
const validateUsername = (input: string): E.Either<ValidationError, string> =>
  pipe(
    E.right(input),
    E.flatMap(nonEmpty),
    E.flatMap(minLength(3)),
    E.flatMap(maxLength(20)),
    E.flatMap(matches(/^[a-zA-Z0-9_]+$/, 'Only letters, numbers, and underscores'))
  )

const validateEmail = (input: string): E.Either<ValidationError, string> =>
  pipe(
    E.right(input),
    E.flatMap(nonEmpty),
    E.flatMap(matches(/^[^\s@]+@[^\s@]+\.[^\s@]+$/, 'Invalid email format'))
  )

Pattern 4: Chaining API Transformations

import * as TE from 'fp-ts/TaskEither'
import { pipe } from 'fp-ts/function'

interface ApiUser { id: number; name: string; email: string }
interface ApiPosts { userId: number; title: string; body: string }[]
interface UserWithPosts { user: ApiUser; posts: ApiPosts; postCount: number }

// API calls as TaskEither
const fetchUser = (id: number): TE.TaskEither<Error, ApiUser> =>
  TE.tryCatch(
    () => fetch(`/api/users/${id}`).then(r => r.json()),
    (e) => new Error(String(e))
  )

const fetchUserPosts = (userId: number): TE.TaskEither<Error, ApiPosts> =>
  TE.tryCatch(
    () => fetch(`/api/users/${userId}/posts`).then(r => r.json()),
    (e) => new Error(String(e))
  )

// Combine into a pipeline
const getUserWithPosts = (userId: number): TE.TaskEither<Error, UserWithPosts> =>
  pipe(
    fetchUser(userId),
    TE.flatMap(user =>
      pipe(
        fetchUserPosts(user.id),
        TE.map(posts => ({
          user,
          posts,
          postCount: posts.length
        }))
      )
    )
  )

// Usage
const program = pipe(
  getUserWithPosts(123),
  TE.map(data => console.log(`${data.user.name} has ${data.postCount} posts`)),
  TE.mapLeft(error => console.error('Failed:', error.message))
)

program()  // Execute the async operation

Pattern 5: Configurable Utilities

// Configuration-driven formatting
interface FormatConfig {
  locale: string
  currency: string
  dateFormat: Intl.DateTimeFormatOptions
  numberFormat: Intl.NumberFormatOptions
}

const createFormatter = (config: FormatConfig) => ({
  currency: (amount: number): string =>
    new Intl.NumberFormat(config.locale, {
      style: 'currency',
      currency: config.currency
    }).format(amount),

  date: (date: Date): string =>
    date.toLocaleDateString(config.locale, config.dateFormat),

  number: (n: number): string =>
    new Intl.NumberFormat(config.locale, config.numberFormat).format(n),

  percent: (n: number): string =>
    new Intl.NumberFormat(config.locale, { style: 'percent' }).format(n)
})

// Create region-specific formatters
const usFormatter = createFormatter({
  locale: 'en-US',
  currency: 'USD',
  dateFormat: { month: 'short', day: 'numeric', year: 'numeric' },
  numberFormat: { maximumFractionDigits: 2 }
})

const euFormatter = createFormatter({
  locale: 'de-DE',
  currency: 'EUR',
  dateFormat: { day: '2-digit', month: '2-digit', year: 'numeric' },
  numberFormat: { maximumFractionDigits: 2 }
})

// Use them
usFormatter.currency(1234.56)  // '$1,234.56'
euFormatter.currency(1234.56)  // '1.234,56 EUR'

Quick Reference

pipe vs flow

// pipe: Start with a value, transform immediately
const result = pipe(value, fn1, fn2, fn3)

// flow: Create a reusable function
const transform = flow(fn1, fn2, fn3)
const result = transform(value)

Creating Composable Functions

// Data-last for pipes
const filter = <A>(pred: (a: A) => boolean) => (arr: A[]): A[] => arr.filter(pred)
const map = <A, B>(fn: (a: A) => B) => (arr: A[]): B[] => arr.map(fn)

// Configuration first, data last
const format = (options: Options) => (value: Value): string => ...

Debug Helpers

const trace = <A>(label: string) => (a: A): A => { console.log(label, a); return a }
const breakpoint = <A>(a: A): A => { debugger; return a }

When to Use

ScenarioApproach
Transform data through stepspipe(data, step1, step2,...)
Create reusable transformflow(step1, step2,...)
Simple single operationRegular function
Multiple unrelated inputsRegular function
Team learning FPStart with pipe, add flow later

Summary

Function composition is about building complex behavior from simple pieces:

  1. Start with pipe - Chain operations on data, read top to bottom
  2. Extract reusable utilities - Small functions that do one thing well
  3. Use data-last - Configuration first, data last enables composition
  4. Know when to stop - Not everything needs to be composed
  5. Debug with trace - Insert logging without breaking the pipeline

The goal isn't to compose everything. The goal is clearer, more maintainable code. Use composition when it helps, skip it when it doesn't.

平台分布

Codex

35.15%
按下载量换算1,433

Claude

27.89%
按下载量换算1,137

Cursor

17.37%
按下载量换算708

Gemini CLI

10.72%
按下载量换算437

安全审计

暂无安全审计结果可展示。

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。目前只有一个公开来源,建议同时核对源仓库。

继续浏览同类 Skills