Community라이팅 & 에디팅github.com

FalkWoldmann/skills

이 스킬은 sans-IO Rust 프로토콜 코어의 동작을 보존하는 구조 재구성에 사용됩니다. quinn-proto, str0m과 같이 `now: Instant`를 받고 `handle_*` 호출로 입력을 공급받으며 `poll_transmit/poll_event/poll_timeout`으로 출력을 배출하는 크레이트를 대상으로 합니다. 상태 머신을 읽기 쉽게 만들거나, 플래그를 상태 열거형으로 정리하거나, 핸들러를 단순화하거나, 코드 명확성을 위해 리팩토링하거나, 큰 파일을 모듈로 분할/재구성할 때 사용하세요. 버그 수정, 기능 추가, 성능 튜닝, PR 검토, 사용자 문서 작성, async/소켓 기반 코드의 sans-IO 변환에는 사용하지 마세요.

skills란 무엇인가요?

이 스킬은 sans-IO Rust 프로토콜 코어를 동작 보존 방식으로 리팩토링하는 전문 도구입니다. sans-IO 라이브러리는 I/O가 분리된 상태 머신으로, 호출자가 `now`와 함께 입력(`handle_*`)을 공급하고 출력(`poll_transmit`, `poll_event`, `poll_timeout`)을 소비하며 소켓과 타이머를 소유합니다. 이 스킬은 상태 머신의 상태, 전이, 출력, 시간이라는 네 가지 핵심 질문에 대한 답을 코드와 타입에서 명확히 볼 수 있게 만듭니다. 작은 단계로 변경하며 각 단계마다 기존 동작과 비교 검증하고, 공개 API를 그대로 유지하며, 종료 시 요약을 제공합니다. quinn-proto, str0m, 해석기, 핸드셰이크, 프레이밍, 클라이언트 코어와 같은 크레이트에 적합하며, 상태 머신을 이해하기 쉽게 만들고자 하는 모든 개발자에게 유용합니다.

지원 대상✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/FalkWoldmann/skills/tree/HEAD/sansio-readability

Installed? Explore more 라이팅 & 에디팅 skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

읽기 쉬운 sans-IO 상태 머신

sans-IO 라이브러리는 I/O가 분리된 상태 머신입니다. 호출자가 입력(handle_*, 각각 now 포함)을 공급하고, 출력(poll_transmit, poll_event, poll_timeout)을 소비하며, 소켓과 타이머를 소유합니다. 이런 코드는 사람들이 추론할 수 있을 때만 가치가 있습니다. 다음 유지보수자, 드라이버 작성자, RFC와 대조 검토하는 리뷰어 모두가 네 가지 질문에 빠르게 답할 수 있어야 합니다:

  1. 상태(States): 각 장기 존재(연결, 쿼리, 스트림, 세션)가 가질 수 있는 상태와 각 상태에 존재하는 데이터는 무엇인가?
  2. 전이(Transitions): 어떤 입력이 한 상태에서 다른 상태로 이동시키며, 각 이동이 코드의 어디에서 발생하는가?
  3. 출력(Outputs): 각 이동이 무엇을 큐에 넣는가(전송, 이벤트, 쓰기), 그리고 어떤 순서인가?
  4. 시간(Time): 언제 깨어나야 하며, 각 데드라인은 어떤 규칙에 의해 설정되는가?

당신의 임무는 머신이 정확히 동일한 동작을 유지하면서 이러한 답을 타입과 코드에서 볼 수 있게 만드는 것입니다. 작업 트리를 편집하고 요약으로 마무리합니다.

<skill>은 이 스킬의 기본 디렉토리, 즉 이 SKILL.md가 있는 폴더를 의미합니다.

기본 규칙

  • 동작은 동일하게 유지됩니다. sans-IO 크레이트에서 동작은 겉보기에 사소해 보이는 것들을 포함합니다:

    • 출력이 큐에 추가되는 순서
    • 맵의 반복 순서(BTreeMap 또는 삽입 순서를 유지하는 Vec이 누가 먼저인지 결정함)
    • poll_timeout이 반환하는 인스턴트
    • 빈 쓰기 또는 no-op 이벤트가 전혀 발생하는지 여부
    • 엔터티가 실패하거나 종료할 때의 오류
    • 스냅샷, 로그 어설션 또는 오류 메시지가 출력하는 경우 변경한 필드가 있는 #[derive(Debug)] 타입의 출력

    공개 API를 통과하는 테스트와 모든 스냅샷은 변경 없이 통과해야 합니다. 하나를 편집해야 한다면 해당 단계가 동작을 변경한 것입니다: 되돌리세요. 크레이트 내부에서 비공개 항목을 참조하는 단위 테스트는 코드와 동일한 기계적 편집이 필요할 수 있습니다(플래그가 변형이 된 경우, 이름이 변경된 헬퍼). 해당 편집만 수행하고 각 어설션이 검사하는 내용을 유지하며 테스트를 요약에 나열하세요.

  • 공개 API를 건드리지 마세요. pub 항목의 이름, 시그니처, 변형 및 필드를 그대로 유지하세요. 드라이버와 다운스트림 크레이트가 이에 의존하고(그리고 semver에 따라) 있기 때문입니다. pub(crate) 및 비공개 항목은 자유롭게 변경할 수 있습니다. API 개선 사항은 요약에 제안으로 넣으세요. 이를 확인하는 방법은 5단계에 있습니다.

  • 가독성을 위해 성능을 희생하지 마세요. 입력당(패킷, 메시지 또는 쿼리당) 실행되는 경로에서 새로운 복제, 할당 또는 맵에 대한 추가 패스를 사용하지 마세요. mem::replace 또는 mem::take는 무료입니다; 빌림 검사기를 회피하기 위해 Vec을 복제하는 것은 무료가 아닙니다.

  • 작은 단계로 작업하세요. 각 단계는 빌드되고 검사를 통과하므로, 실패한 검사는 하나의 변경을 가리키며 diff를 조각별로 검토할 수 있습니다.

  • 프로젝트를 따르세요. 편집하기 전에 에이전트 및 기여자 문서(AGENTS.md, CLAUDE.md, CONTRIBUTING), 린트 구성 및 CI 워크플로우를 읽으세요. 주변의 주석 밀도와 관용구를 일치시키세요. 프로젝트가 코드와 함께 문서를 업데이트하는 경우(생명주기 표, 행을 함수에 매핑하는 주석, 사양 적용 범위 표), 동일한 단계에서 업데이트하세요.

작업 크기를 변경에 맞게 조정

안전망은 기존 테스트가 놓칠 동작 변경을 잡기 위해 존재하므로, 계획한 변경에 맞게 크기를 조정하세요:

  • 작음(Small). 하나의 핸들러 또는 몇 개의 헬퍼, 상태를 보유하는 필드에 변경 없음. 프로젝트의 검사를 한 번 전과 한 번 후에 실행하고, 아래 큼으로 표시된 모든 것은 건너뛰세요. 요약에서는 건드린 엔터티만 매핑하세요.
  • 중간(Medium). 하나의 모듈에서 여러 전이 또는 출력 헬퍼. 각 단계 전과 마지막에 검사를 실행하세요. 차등 하네스는 변경하는 경로의 정확한 출력을 고정하는 기존 테스트가 없는 경우에만, 그리고 해당 경로에 대해서만 구축하세요. 퍼징이나 추가 속성 테스트 케이스, 벤치마크는 없습니다: 프로젝트 자체의 검사를 일반 설정으로 사용하는 것이 여기서 적절한 무게입니다.
  • 큼(Large). 플래그가 단계 열거형으로 대체, 상태 필드의 많은 작성자가 전이로 병합, 대부분의 핸들러가 재구성됨. 전체 워크플로우를 사용하세요.

크기는 매핑(2단계) 후에 결정하세요. 변경이 중간에 커지면 크기를 한 단계 올리고 요약에 그렇게 명시하세요.

워크플로우

1. 안전망 설정

첫 번째 편집 전에 이 작업을 수행하여 이후의 모든 실패가 기존 문제가 아닌 당신의 것임을 보장하세요.

  • 기준점 기록. git status를 실행하고 기준 커밋을 기록하세요: 이후 모든 비교는 이 기준과 이루어집니다. 건드릴 파일에 이미 커밋되지 않은 변경 사항이 있으면, 편집을 섞기 전에 사용자에게 알리세요. 그 위에서 진행하는 경우 git stash create를 기준으로 사용하세요: 트리를 건드리지 않고 커밋으로 기록합니다.
  • 하나의 검사 명령 수정. 프로젝트 자체의 명령이 있으면 사용하세요(CI 워크플로우, justfile, xtask, 에이전트 문서의 명령). 그렇지 않으면 번들된 게이트를 사용하세요: <skill>/scripts/check.sh <repo> default all. 게이트는 서식을 변경하지 않고 확인한 다음 각 기능 세트에 대해 clippy와 테스트를 실행하고, 실패하면 0이 아닌 종료 코드를 반환합니다. CI가 사용하는 기능 세트를 나열하세요(예: default all none tls). 게이트는 변경하는 패키지와 그에 의존하는 멤버로 범위를 좁히려면 CHECK_PACKAGES="-p <crate> -p <dependent>"를 사용하고, 마지막에 전체 워크스페이스를 한 번 실행하세요. CI가 다른 것도 실행하는 경우(MSRV 검사, 추가 린트 플래그), 그것도 함께 실행하세요.
  • 기준선이 녹색이 아니면 수정하지 마세요. 어떤 검사가 어떻게 실패하는지 기록하고 사용자에게 알린 후, 그 이후부터는 동일한 실패를 기대하고 새로운 실패가 없도록 하세요. 편집하는 파일만 서식을 지정하세요. 환경에서 발생한 실패(야간 전용 플래그가 있는 글로벌 cargo 구성, 누락된 툴체인 또는 도구)는 이유와 함께 "실행할 수 없음"으로 Verification 아래에 넣으세요. 리포지토리에 CI가 없으면 rust-version에서 툴체인 최소 버전, 에디션 및 사용 중인 언어 기능을 가져와 실행할 수 있었던 것과 실행할 수 없었던 것을 말하세요.
  • 큰 변경: 차등 하네스 구축 기존 테스트가 변경할 경로의 정확한 출력을 고정하지 않는 경우. 고정은 출력 순서와 데드라인을 문자 그대로 단언하는 추적 또는 스냅샷 테스트를 의미합니다; 최종 상태에 대한 단언만으로는 충분하지 않습니다. 하네스는 기준과 작업 트리를 동일한 무작위 호출에 대해 나란히 실행하고 모든 출력을 비교합니다. <skill>/scripts/difftest_scaffold.py는 리포지토리 외부에 크레이트 쌍과 템플릿을 생성합니다. 템플릿을 채우는 것이 실제 작업이므로 예산을 세우세요: 먼저 references/differential.md를 읽으세요. 의존하기 전에 심은 한 줄짜리 돌연변이 하나를 잡는지 확인하세요; 하나면 충분합니다.
  • 하네스를 리팩토링보다 저렴하게 유지하세요. 전체 크레이트가 아닌 구조화하는 경로를 구동하세요. 경로를 다루는 비용이 변경 비용보다 더 드는 경우, Verification 아래에 그 차이를 보고하고 해당 경로의 기존 테스트에 의존하세요. 예: TLS 또는 암호화 뒤의 경로, 정규화해야 하는 비결정성, 시뮬레이션해야 하는 피어.
  • 큰 변경만: 느린 검사를 더 많은 케이스로 한 번 실행. 속성 테스트를 몇 분 안에 끝나도록 크기 조정(기본값의 10배, 또는 케이스가 저렴할 때 PROPTEST_CASES=10000)하여 실행하고, 마지막에 다시 실행하세요. 변경할 코드에 도달하는 경우에만 퍼즈 타겟을 실행하고, 30~60초로 시간 제한을 두세요. 변경이 입력당 경로에 작업을 추가하는 경우에만 벤치마크를 실행하세요; 제어 흐름 재구성에는 필요하지 않습니다.
  • 변경 후에도 남아야 하는 적용 범위 차이를 채우세요. 구조화할 경로에 적용 범위가 없고 하네스를 구축하지 않는 경우, 먼저 현재 출력을 고정하는 테스트를 추가하세요. 하네스를 구축하는 경우 리팩토링을 위한 차이를 다룹니다; 발견한 동작 중 유지할 가치가 있는 것(의도적으로 유지한 특이한 동작)에 대해서만 테스트를 추가하세요. 새 테스트는 기존 테스트가 변경되지 않도록 새 파일 또는 모듈에 자체 단계로 넣으세요. 예외는 단일 크레이트 테스트 레이아웃(예: tests/main.rs)에 필요한 하나의 mod 줄입니다.

2. 머신 매핑

요청과 관련된 엔터티와 계획된 변경이 영향을 미칠 모든 엔터티를 매핑하세요. 요청이 어떤 부분이 중요한지 명시하지 않은 큰 크레이트의 경우, 사용자가 이름을 지정한 엔터티를 선택하고 무엇을 생략했는지 말하세요. 변경하기 전에 맵을 작성하세요(스크래치 파일도 괜찮습니다). 이는 요약의 상태 맵이 되며, 종종 모듈 문서가 됩니다.

  • 엔터티. 범위 내 각 엔터티에 대해 필드를 나열하고 의미가 있는 단계별로 그룹화하세요. 단계를 인코딩하는 bool과 Option의 조합이 주요 대상입니다.
  • 입력. 외부 데이터 또는 now를 취하는 모든 pub fn과 각각이 무엇을 트리거할 수 있는지.
  • 작성자. 각 상태 필드에 대해 할당하는 모든 위치(.field =, mem::replace, .take(), += 1 검색). 작성자가 많은 필드는 파일 전체에 흩어진 전이입니다.
  • 출력. 출력 큐에 푸시되는 모든 것과 poll_timeout이 읽는 모든 데드라인.
  • 불변 조건. 항상 유지되어야 하는 것: 정확히 하나의 맵에 있는 ID, 무언가가 기다릴 때마다 설정된 데드라인, 존재하는 참조된 연결.
  • 특이한 점. 우연처럼 보이는 동작: 관대한 파싱, 두 오류 간의 이상한 우선순위, 가비지에 대한 응답으로 보낸 응답, 데드라인 이후에도 여전히 허용되는 입력. 유지하지만(규칙 1), 사용자를 위해 나열하세요.

그런 다음 맵을 표로 작성하세요: 상태, 입력, 다음 상태, 출력. 여러 함수를 추적하지 않고 코드에서 채울 수 없는 경우, 코드가 아직 읽기 쉽지 않은 것이며, 그곳이 작업이 필요한 곳입니다.

3. 변경 선택

references/patterns.md는 카탈로그로, 전/후 예제와 각 패턴이 효과적이지 않은 경우를 포함합니다. 색인을 읽은 다음 맵이 찾아낸 내용에 대한 섹션을 읽고, "가치 없음" 단락도 포함하세요. 큰 변경의 경우 전체 파일을 읽으세요. 각 변경이 네 가지 질문 중 하나에 대한 답을 얼마나 단축하는지와 diff의 크기를 기준으로 후보 변경의 순위를 매기세요. 일반적으로 가장 효과적인 변경:

  • 각 단계의 데이터를 전달하는 변형이 있는 열거형, 단계를 인코딩하는 플래그와 옵션 대신(패턴 1)
  • 각 전이에 대해 명명된 함수 하나씩, 각 상태 필드에 작성자가 거의 없도록(패턴 2)
  • 입력을 일치시키고, 결정하고, 전이를 적용하고, 출력을 큐에 추가하는 핸들러, 일관된 디스패치 축을 따라(패턴 3)
  • 의도에 따라 명명된 각 출력 종류당 하나의 헬퍼(패턴 4)
  • 각 데드라인 규칙이 한 번 작성되고, poll_timeout이 상태에서 파생됨(패턴 5)
  • 생명주기가 독자가 도착하는 곳에 기록되고, 필드에 대한 불변 조건(패턴 9)

사용자가 하나의 함수 또는 하나의 부분을 명명한 경우, 해당 부분과 필요한 것만 변경하세요. 추출한 헬퍼는 자연스러운 형제에 의해 재사용될 수 있지만, 거기에도 헬퍼가 맞는다고 해서 요청 외부의 코드를 재작업하지 마세요. 대신 해당 개선 사항을 제안 아래에 나열하세요: 한 함수에 대해 물어본 리뷰어가 두 번째 함수를 확인할 필요가 없어야 합니다.

코드만 이동하는 변경, 취향에 맞는 지역 변수 이름 변경, 질문에 답하기 더 쉽게 만들지 않으면서 코드를 길게 만드는 모든 것은 제외하세요. 사용자가 이동 자체(긴 파일을 모듈로 분할)를 요청하는 경우 패턴 11을 참조하세요. 이미 잘 읽히는 코드는 그대로 둘 수 있습니다: 작업을 만들지 말고 요약에 그렇게 명시하세요.

4. 단계별 적용

각 단계가 검토 가능하고 이후 단계에서 삭제할 코드를 추가하지 않도록 순서를 정하세요:

  1. 순수 추가 및 추출: 출력 헬퍼, 명명된 조건자, 데드라인 헬퍼, 기능 게이트된 단계를 헬퍼로 이동, 와이어 인코딩 및 파싱을 함수로 이동(패턴 4, 5, 6, 10).
  2. 핵심 변경: 단계 열거형과 이에 의해 강제되는 전이 및 핸들러 형태(패턴 1~3, 7, 12). 이는 종종 한 단계로 적용되어야 합니다. 그렇다면 이유를 설명하세요.
  3. 이름(패턴 8).
  4. 문서(패턴 9).
  5. 메서드 재정렬, 마지막으로, 그리고 독립적으로(패턴 11).

편집 사이에는 빠른 루프를 사용하세요: 기본 기능 세트에서 변경하는 패키지의 clippy와 테스트, 그리고 하네스를 구축한 경우 하네스도 포함. 각 단계가 끝날 때 1단계의 검사 명령을 실행하세요. 그런 다음 사용자가 커밋을 원하면 단계를 커밋으로 만드세요(요청했거나 프로젝트의 워크플로우가 기대하는 경우); 그렇지 않으면 작업을 커밋되지 않은 상태로 두고 단계를 요약에 나열하세요. 다음 단계에서 제거할 스캐폴딩으로 린트를 조용히 하지 마세요(하나의 단계를 통과시키기 위해 추가된 cfg 게이트 또는 #[allow]). 대신 단계를 재정렬하세요.

빌림 검사기가 강제로 왜곡을 만드는 지점에서는 패턴을 포기하세요. 왜곡된 코드는 원래보다 더 읽기 어렵기 때문입니다. 일반적인 징후:

  • self의 형제 필드(출력 큐: &mut VecDeque<Transmit>) 또는 자신의 맵 키를 인수로 가져 빌림을 피하는 메서드
  • 하위 구조체만 읽을 수 있도록 &self 메서드가 하위 구조체를 가져오는 경우
  • 복제된 키 또는 추가 조회

일반적인 수정:

  • 출력을 반환하고 호출자가 큐에 추가하게 함
  • 엔터티에 헬퍼를 배치하고 필요한 Copy 구성 값을 전달
  • 코드를 인라인으로 유지

5. 검증 및 요약

검사 명령을 마지막으로 한 번, 전체 워크스페이스에 대해 실행하고, 시작 시 실행한 다른 모든 것(느린 검사 및 하네스)도 함께 실행하세요.

그런 다음 diff가 pub 항목, derive, 특성 구현, 필드 타입(auto 특성(예: Send)은 비공개 필드를 따름) 또는 항목의 cfg에 닿는 경우 공개 API를 확인하세요:

  • cargo semver-checks --baseline-rev <base>를 각 기능 세트당 한 번 사용하고, 명시적 플래그(--default-features, --all-features, --only-explicit-features (없음), --only-explicit-features --features x)를 사용하세요.
  • 이는 호환성을 깨는 변경만 잡으므로, 새로운 것이 공개되지 않았는지도 확인하세요: 설치된 경우 cargo public-api diff를 사용하거나, git diff <base> | grep -E '^\+.*\bpub\b'를 읽으세요.
  • 도구가 설치되지 않은 경우 설치하기 전에 요청하고, 그렇지 않으면 API 검사를 실행되지 않음으로 보고하세요.
  • diff가 함수 본문과 비공개 항목에만 닿으면 그렇게 말하세요.

API 체크 후, 행동 체크를 실행하고 모든 실행 결과를 수집하세요. 마지막으로 요약을 작성하세요:

## 요약

### 변경 사항

- (변경 사항 목록)

### 상태 맵

(각 엔터티에 대한 표: 상태, 입력, 다음 상태, 출력)

### 검증

- 기준 커밋: ...
- 검사: (체크 상태)
- API 변경: (없음 / 사소함 / 확인되지 않음)
- 적용 범위 차이: (해당되는 경우)

### 제안

(향후 개선 사항 목록)

관련 스킬