- 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>
4.8 KiB
4.8 KiB
paths
| paths | |||||
|---|---|---|---|---|---|
|
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을 같이 바꾼다.