Files
networkSetup/.claude/rules/rust.md
T
jychoi4andClaude Opus 5.5 905e6d4eaa chore: add Claude Code guidelines, rules and skills
- CLAUDE.md: product principles, crate layering, common commands, commits.
- .claude/rules: common, Rust, frontend, Tauri IPC, docs and security rules.
- .claude/skills: check, add-tauri-command, project-model-change,
  change-record, msix-integration-test, backlog-status.

Refs #16

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 11:09:05 +09:00

4.8 KiB

paths
paths
src/crates/**/*.rs
src/apps/cli/**/*.rs
src/apps/desktop/src-tauri/**/*.rs
src/Cargo.toml
src/**/Cargo.toml

Rust 규칙

도구 체인은 rust-toolchain.toml의 1.94.0 고정. cargo fmt 기본 설정과 clippy -D warnings를 통과해야 한다. 별도 rustfmt.toml/clippy.toml을 추가하지 않는다.

크레이트 경계

크레이트 허용 금지
package-domain serde DTO, 순수 검증, manifest 문자열 생성 std::fs, std::process, anyhow, OS API
package-engine 계약 타입, PackageBackend 트레이트, 계획 생성, 해시 유틸 파일시스템 직접 접근(트레이트로 위임)
package-infra 파일·프로세스·잠금·레지스트리·서명·저장 Tauri·clap 타입
apps/cli, src-tauri 인자 파싱, 세션 상태, 오류 → 문자열 변환 비즈니스 로직, 해시·검증 재구현

새 외부 crate는 [workspace.dependencies]에 먼저 추가하고 멤버에서 .workspace = true로 참조한다. Windows 전용 API는 windows-sys의 feature를 명시적으로 추가하고 #[cfg(windows)]로 감싼다. 비Windows 빌드가 컴파일되게 유지한다.

DTO

  • 모든 직렬화 타입: #[derive(Debug, Clone, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")]. 사용자 파일에서 읽는 타입은 deny_unknown_fields를 반드시 붙인다. 외부로 내보내기만 하는 타입(Validation, BuildResult, JobRecord)은 생략 가능.
  • package-domain의 단순 구조체는 model! 매크로를 쓴다. 기본값·enum·문서 주석이 필요하면 매크로 없이 직접 선언한다.
  • 기존 v2 파일을 깨지 않고 필드를 추가할 때만 #[serde(default)]를 쓴다. 필드 제거는 자동 변환 없이 알 수 없는 필드 오류로 거부한다(projectId 선례).
  • JSON Schema(schemars::JsonSchema)는 Project 트리에 포함되는 모든 타입에 derive한다. 변경 후 schemas/package-project.schema.json을 재생성한다(project-model-change 스킬).
  • u64 바이트 수를 IPC로 보내면 안 된다. ResolvedFile.bytes처럼 String으로.

오류

  • 라이브러리 크레이트는 anyhow::Result. ensure!, bail!, .context()/.with_context()로 ErrorCode: 설명 형식을 만든다.
  • 경로를 메시지에 넣을 때는 path.display(). 민감한 값(지문 전체, 토큰)은 넣지 않는다.
  • unwrap()은 테스트와 불변 조건이 보장된 곳(path.parent() 직후의 .unwrap()처럼 바로 위에서 확인한 경우)에서만. 그 외는 .context("MissingParent")?.
  • let _ = child.kill();처럼 정리 경로에서 결과를 무시할 때만 let _ =를 쓴다.

파일·프로세스 안전

  • 쓰기는 항상 atomic_json/atomic_bytes 경유: 같은 디렉터리 임시 파일 → sync_all → persist. fs::write로 결과 파일을 직접 쓰지 않는다(스테이징 폴더 내부 임시 파일은 예외).
  • 사용자 경로를 열기 전 reject_links. 프로젝트 폴더를 바꾸는 작업은 lock_project를 먼저 잡고 _lock 바인딩을 함수 끝까지 유지한다.
  • 외부 도구 실행은 process::run만 사용한다. 허용 도구는 절대 경로의 makeappx.exe, signtool.exe, PowerShell 서명 스크립트. 인자는 &[&OsStr] 배열. 셸 문자열 결합 금지. 로그 파일 경로를 반드시 받는다.
  • 긴 루프(파일 복사, 해시, 역추출 검사)는 반복마다 cancelled(cancel)?를 호출한다.
  • 해시 비교는 계획에 고정된 값과 실행 전 소스, 복사 중 스트림, 역추출 결과 세 지점에서 한다. 하나를 생략하지 않는다.

테스트

  • 통합 테스트는 crates/<name>/tests/*.rs. tempfile::tempdir()로 격리하고 _temp 바인딩을 유지한다. fixture() 헬퍼를 재사용한다.
  • MakeAppx/SignTool이 필요한 테스트는 #[ignore] + std::env::var("MSIX_TEST_MAKEAPPX").
  • 거부 테스트는 오류 문자열의 ErrorCode를 .contains("UnsupportedProjectExtension")처럼 확인한다. 메시지 전문을 비교하지 않는다.
  • 시험 PC의 실제 레지스트리·인증서 저장소·%APPDATA%를 바꾸는 테스트를 만들지 않는다. RecentStore처럼 경로를 주입받는 구조로 만든다.

스타일

  • use 정렬은 rustfmt에 맡긴다. std::{fs, path::{Path, PathBuf}} 묶음 형식을 따른다.
  • 작은 private 헬퍼(error, idle, document)는 파일 상단에 모은다.
  • 매직 넘버는 이름을 붙인다: MAX_RECENT, 타임아웃 Duration::from_secs(900)은 상수로 올릴 때 README의 "15분 제한"과 함께 바꾼다.
  • 문자열 상태("ArtifactVerified", "Interrupted", "notRun")는 JSON 계약이다. 바꾸면 TS 타입, README, 검증 JSON을 같이 바꾼다.