Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .cursor/hooks/after-file-edit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
# afterFileEdit hook: run lint --fix on edited file(s).
# Fail-open: always exit 0 so agent flow is not blocked.

set -e

ROOT="${CURSOR_PROJECT_DIR:-.}"
INPUT=$(cat)

get_file_path() {
node -e "
try {
const d = JSON.parse(process.argv[1]);
const p = d.file_path;
if (p && typeof p === 'string') process.stdout.write(p);
} catch (_) {}
" "$INPUT"
}

file_path=$(get_file_path)
if [[ -z "$file_path" || ! -f "$file_path" ]]; then
exit 0
fi

# Convert to project-relative path for lint
if [[ "$file_path" == "$ROOT"/* ]]; then
rel_path="${file_path#"$ROOT"/}"
else
rel_path="$file_path"
fi

# Only lint supported extensions; redirect output to log so hook produces no stdout
case "$rel_path" in
*.ts|*.tsx|*.js|*.jsx|*.mjs|*.cjs)
mkdir -p "$ROOT/.cursor/hooks/logs"
(cd "$ROOT" && npx expo lint --fix -- "$rel_path") >>"$ROOT/.cursor/hooks/logs/after-file-edit.log" 2>&1 || true
;;
esac

exit 0
8 changes: 8 additions & 0 deletions .cursor/hooks/session-init.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash
# sessionStart hook: inject additional_context so the agent
# does not unnecessarily run npm run lint / npm run typecheck.

node -e "
const msg = '이 프로젝트에서는 파일 편집 후 자동으로 lint --fix가 실행되며, 세션 종료 시 typecheck와 lint 결과가 .cursor/hooks/logs/ 에 기록됩니다. 사용자가 요청하지 않는 한 npm run lint / npm run typecheck 를 직접 실행하지 마세요.';
console.log(JSON.stringify({ additional_context: msg }));
"
39 changes: 39 additions & 0 deletions .cursor/hooks/stop-audit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# stop hook: run typecheck + lint and write audit log.
# Does not block agent (no followup_message).

set -e

ROOT="${CURSOR_PROJECT_DIR:-.}"
INPUT=$(cat)
LOG_DIR="$ROOT/.cursor/hooks/logs"
mkdir -p "$LOG_DIR"
TS=$(date +%Y%m%d-%H%M%S)
LOG_FILE="$LOG_DIR/audit-$TS.log"

session_info() {
node -e "
try {
const d = JSON.parse(process.argv[1]);
const id = d.conversation_id || '';
const status = d.status || '';
const loop = d.loop_count ?? '';
console.log('conversation_id:', id);
console.log('status:', status);
console.log('loop_count:', loop);
} catch (_) {}
" "$INPUT"
}

{
echo "=== Audit $TS ==="
echo "--- Session ---"
session_info
echo "--- Typecheck ---"
(cd "$ROOT" && npm run typecheck 2>&1) || true
echo "--- Lint ---"
(cd "$ROOT" && npm run lint 2>&1) || true
} >> "$LOG_FILE"

# stop hook may output JSON; we output empty object
echo '{}'
7 changes: 7 additions & 0 deletions .cursor/rules/convention.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
alwaysApply: true
---

# 프로젝트 공통 규칙

- **프론트엔드 설계**: `.ts`/`.tsx` 작성 시 [frontend-design.mdc](.cursor/rules/frontend-design.mdc)(Toss 프론트엔드 가이드 — 가독성, 예측 가능성, 응집도, 결합도)를 참고한다.
81 changes: 81 additions & 0 deletions .cursor/rules/export-import.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
---
description: Barrel export, import 순서, type import, 순환 참조 규칙
globs: "**/*.ts,**/*.tsx"
alwaysApply: false
---

# Export/Import 규칙

## Barrel export

- 상위 레이어에서는 **반드시 barrel(index)만 import**한다. 예: `import { X } from "@/features/diary"`. (자세한 레이어/의존성은 [fsd-structure.mdc](.cursor/rules/fsd-structure.mdc) 참고.)
- **와일드카드**: 해당 모듈의 **모든 요소를 그대로** re-export할 때만 `export * from "./x"` 사용.
- **일부만 노출**하거나, 이름 변경·내부 전용 제외 시에는 `export { A, B } from "./x"` 형태로 **중괄호 named export** 사용.
- `index.ts`에는 **공개 API만** 노출하고, 내부 전용 심볼은 export하지 않는다.

```ts
// ✅ 전부 노출할 때
export * from "./handler";
export * from "./schema";

// ✅ 일부만 노출할 때
export { signUp, signIn } from "./handler";
export type { SignUpRequest, User } from "./schema";
```

```ts
// ❌ 내부 경로 직접 import
import { DiaryRecorder } from "@/features/diary/ui/diary-recorder";
```

## Import 순서

일반적인 관행을 따른다. 그룹 사이 빈 줄 권장.

1. **builtin** — Node 내장 (해당 시)
2. **external** — 외부 패키지 (react, expo, third-party)
3. **internal** — 프로젝트 alias (`@/...`)
4. **parent/sibling/index** — 상대 경로 (`../`, `./`)

같은 그룹 내 정렬은 팀/도구 기본값(예: `eslint-plugin-import`의 `import/order`)을 따른다.

```ts
import * as Notifications from "expo-notifications";

import { queries } from "@/entities";
import { MainLayout } from "@/shared/ui";

import { formatDate } from "./lib/format-date";
```

## Type import

- **타입만 사용하는 심볼** → `import type { X, Y } from "..."` 사용.
- **값과 타입을 같이 쓸 때** → `import { type X, doSomething } from "..."` (인라인 `type`) 허용.

```ts
// ✅ 타입만
import type { SignUpRequest, ChatMessage } from "@/entities/user/api/schema";

// ✅ 값 + 타입
import { createReport, type WeeklyReportItem } from "@/entities/chatbot/api";
```

```ts
// ❌ 타입만 쓰는데 일반 import
import { SignUpRequest } from "@/entities/user/api/schema";
```

## 순환 참조 방지

- 레이어/슬라이스 간에는 **FSD 의존성 방향만** 허용 (fsd-structure 참고).
- **Barrel 주의**: `index.ts`는 가능한 한 **re-export만** 하고, barrel 자체에서 로직·import는 최소화한다. 같은 폴더 모듈이 상위 barrel이나 다른 슬라이스를 참조하면 순환이 생기기 쉽다.
- 복잡한 집계(예: `mergeQueryKeys`)는 별도 모듈로 두고, barrel은 그 모듈만 re-export하도록 한다.
- 필요 시 `madge --circular src/` 등으로 순환 검사 가능.

## ESLint 연동 (선택)

- `eslint-plugin-import`의 **import/order**로 위 순서 자동화.
- `@typescript-eslint/consistent-type-imports`로 type-only import 강제.

규칙 문서는 팀 합의를 담고, ESLint는 그 합의를 자동 적용하는 수단이다.
Loading