.claude/skills/verify-filters/SKILL.md
Verifies URL-driven filter SSOT pattern compliance — filter-utils required exports, hook existence, page.tsx server-side parsing, no useState for filter state (URL params are SSOT). Run after adding/modifying list page filters.
npx skillsauth add junnv93/equipment_management_system verify-filtersInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
목록 페이지의 필터가 URL-driven SSOT 3계층 아키텍처를 올바르게 준수하는지 검증합니다:
{feature}-filter-utils.ts가 필수 4개 함수를 모두 export하는지use-{feature}-filters.ts 훅이 filter-utils를 소비하는지parseFiltersFromSearchParams(searchParams) 호출하는지| File | Purpose |
| ------------------------------------------------------------------------- | ------------------------------------ |
| apps/frontend/lib/utils/equipment-filter-utils.ts | 참조 구현 (가장 완전한 패턴) |
| apps/frontend/lib/utils/calibration-filter-utils.ts | 교정 필터 유틸리티 |
| apps/frontend/lib/utils/calibration-plans-filter-utils.ts | 교정계획 필터 유틸리티 |
| apps/frontend/lib/utils/team-filter-utils.ts | 팀 필터 유틸리티 |
| apps/frontend/lib/utils/notification-filter-utils.ts | 알림 필터 유틸리티 |
| apps/frontend/lib/utils/role-filter-utils.ts | 역할별 기본 필터 리다이렉트 유틸리티 |
| apps/frontend/hooks/use-user-preferences.ts | 클라이언트 사용자 설정 훅 (withPreferences 소스) |
| apps/frontend/lib/api/preferences-server.ts | 서버 사이드 사용자 설정 조회 (withPreferences 소스) |
| apps/frontend/hooks/useEquipmentFilters.ts | 장비 필터 훅 (레거시 이름 규칙) |
| apps/frontend/lib/utils/filter-select-utils.ts | useFilterSelect 훅 — Radix Select spurious onValueChange 차단 (open 상태 ref 추적) |
| apps/frontend/hooks/use-calibration-filters.ts | 교정 필터 훅 |
| apps/frontend/hooks/use-calibration-plans-filters.ts | 교정계획 필터 훅 |
| apps/frontend/hooks/use-team-filters.ts | 팀 필터 훅 |
| apps/frontend/hooks/use-notification-filters.ts | 알림 필터 훅 |
| apps/frontend/lib/utils/audit-log-filter-utils.ts | 감사 로그 필터 유틸리티 |
| apps/frontend/lib/utils/checkout-filter-utils.ts | 반출 필터 유틸리티 |
| apps/frontend/lib/utils/non-conformances-filter-utils.ts | 부적합 필터 유틸리티 |
| apps/frontend/lib/utils/software-filter-utils.ts | 소프트웨어 필터 유틸리티 (hook 미존재 — 예외 #10) |
| apps/frontend/lib/utils/reports-filter-utils.ts | 보고서 필터 유틸리티 + convertFiltersToApiParams |
| apps/frontend/hooks/use-nc-filters.ts | 부적합 필터 훅 |
| apps/frontend/hooks/use-reports-filters.ts | 보고서 필터 훅 |
| apps/frontend/app/(dashboard)/equipment/page.tsx | 서버 컴포넌트 파싱 참조 구현 |
| apps/frontend/app/(dashboard)/checkouts/page.tsx | 반출 페이지 서버 파싱 |
| .claude/skills/equipment-management/references/filter-utils-template.md | 필터 유틸리티 템플릿 |
모든 *-filter-utils.ts 파일이 필수 4개 함수를 export하는지 확인합니다.
# 모든 filter-utils 파일 목록
ls apps/frontend/lib/utils/*-filter-utils.ts
각 파일에 대해 필수 export를 확인합니다:
# 필수 export 확인 (role-filter-utils.ts 제외 — 리다이렉트 전용)
for f in $(ls apps/frontend/lib/utils/*-filter-utils.ts | grep -v role-filter); do
echo "=== $f ==="
missing=""
grep -q "parseFiltersFromSearchParams\|parse.*FiltersFromSearchParams" "$f" || missing="$missing parseFromSearchParams"
grep -q "convertFiltersToApiParams\|convertToApiParams" "$f" || missing="$missing convertToApiParams"
grep -q "countActiveFilters" "$f" || missing="$missing countActiveFilters"
grep -q "DEFAULT_UI_FILTERS\|getDefaultUIFilters" "$f" || missing="$missing DEFAULT_UI_FILTERS"
if [ -n "$missing" ]; then
echo "MISSING:$missing"
else
echo "OK"
fi
done
PASS 기준: 모든 filter-utils 파일에 4개 필수 export가 존재.
FAIL 기준: 하나라도 누락되면 해당 함수 추가 필요.
각 filter-utils 파일이 UI{Feature}Filters와 Api{Feature}Filters 인터페이스 쌍을 export하는지 확인합니다.
# UI/API 필터 타입 쌍 확인
for f in $(ls apps/frontend/lib/utils/*-filter-utils.ts | grep -v role-filter); do
echo "=== $f ==="
ui=$(grep -c "export interface UI.*Filters\|export type UI.*Filters" "$f")
api=$(grep -c "export interface Api.*Filters\|export type Api.*Filters" "$f")
if [ "$ui" -eq 0 ] || [ "$api" -eq 0 ]; then
echo "MISSING: UI=$ui, API=$api"
else
echo "OK: UI=$ui, API=$api"
fi
done
PASS 기준: 모든 파일에 UI 및 API 필터 인터페이스가 존재.
각 filter-utils에 대응하는 filter hook이 존재하는지 확인합니다.
# filter-utils에 대응하는 hook 존재 확인
for f in $(ls apps/frontend/lib/utils/*-filter-utils.ts | grep -v role-filter); do
feature=$(basename "$f" | sed 's/-filter-utils.ts//')
# useEquipmentFilters는 레거시 이름 규칙
hook=$(ls apps/frontend/hooks/use-${feature}-filters.ts apps/frontend/hooks/use${feature^}Filters.ts apps/frontend/hooks/useEquipmentFilters.ts 2>/dev/null | head -1)
if [ -z "$hook" ]; then
echo "MISSING HOOK for: $feature"
else
echo "OK: $feature → $hook"
fi
done
# hook이 filter-utils를 import하는지 확인
grep -rn "from.*filter-utils\|from.*filter-utils" apps/frontend/hooks/use*-filters.ts apps/frontend/hooks/useEquipmentFilters.ts 2>/dev/null
PASS 기준: 모든 filter-utils에 대응하는 hook이 존재하고, hook이 utils를 import.
목록 페이지의 page.tsx가 서버 컴포넌트에서 searchParams를 파싱하는지 확인합니다.
# 주요 목록 페이지에서 parseFiltersFromSearchParams 호출 확인
grep -rn "parse.*FiltersFromSearchParams" apps/frontend/app --include="page.tsx"
PASS 기준: 필터가 있는 목록 페이지(equipment, calibration, calibration-plans, teams, checkouts)의 page.tsx에서 파싱 함수 호출.
목록 페이지의 클라이언트 컴포넌트가 필터 상태를 useState로 관리하지 않는지 확인합니다.
# 필터 관련 useState 탐지 (Content.tsx 파일)
grep -rn "useState.*site\|useState.*status\|useState.*team\|useState.*category\|useState.*search\|useState.*filter" apps/frontend/app --include="*Content.tsx" | grep -v "// \|selectedSite\|isSearch\|searchTerm\|filterOpen\|showFilter\|FilterDialog"
참고: searchTerm(검색어 입력), filterOpen(필터 UI 토글) 등 UI 전용 상태는 정상.
PASS 기준: Content.tsx에서 필터 파라미터(site, status, team, category)를 useState로 관리하지 않아야 함.
FAIL 기준: useState<string>(site), useState(selectedStatus) 등 필터 값을 useState로 관리하면 URL 파라미터 SSOT 위반.
filter-utils에 withPreferences 함수가 있는 경우, 서버(page.tsx)와 클라이언트(filter hook) 양쪽에서 동일하게 사용되는지 확인합니다.
# withPreferences 함수가 있는 filter-utils 확인
grep -rn "export function withPreferences" apps/frontend/lib/utils/*-filter-utils.ts
# withPreferences를 사용하는 서버/클라이언트 파일 확인
grep -rn "withPreferences" apps/frontend/app --include="page.tsx"
grep -rn "withPreferences" apps/frontend/hooks --include="*.ts"
PASS 기준: withPreferences가 정의된 경우, 대응하는 page.tsx(서버)와 filter hook(클라이언트)에서 모두 사용.
FAIL 기준: 서버 또는 클라이언트 한쪽에서만 사용하면 초기 fetch와 클라이언트 fetch 간 불일치 발생.
참고: withPreferences는 URL 필터가 아닌 사용자 설정(DisplayPreferences)을 API 파라미터에 병합하는 함수. showRetired 같은 preference 기반 필터에 사용.
| # | 검사 | 상태 | 상세 |
| --- | ------------------------- | --------- | -------------------- |
| 1 | filter-utils 필수 export | PASS/FAIL | 누락 함수 목록 |
| 2 | UI/API 인터페이스 쌍 | PASS/FAIL | 누락 인터페이스 목록 |
| 3 | filter hook 존재 | PASS/FAIL | 누락 hook 목록 |
| 4 | page.tsx 서버 파싱 | PASS/FAIL | 누락 page.tsx 목록 |
| 5 | Content.tsx useState 금지 | PASS/FAIL | 위반 위치 목록 |
다음은 위반이 아닙니다:
use-{feature}-filters.ts (kebab-case) 사용useSearchParams와 별개로 입력 중간값 관리page.tsx에서 직접 서버 파싱 후 Content에 props 전달하는 패턴. 별도 클라이언트 hook 불필요 (admin 전용 페이지, URL 직접 조작 없음)CheckoutsContent.tsx가 useSearchParams()로 직접 파싱하는 패턴 (parseCheckoutFiltersFromSearchParams(searchParams) 사용). 별도 hook 불필요 (탭 전환 시 필터 리셋 로직이 Content에 포함됨)TestSoftwareListContent.tsx가 useSearchParams()로 직접 파싱하는 패턴 (parseTestSoftwareFiltersFromSearchParams(searchParams) 사용). 별도 hook 불필요 (단일 목록 페이지, 필터 리셋 로직이 Content에 포함됨)testing
Verifies Zod validation pattern compliance — ZodValidationPipe usage (no class-validator), versionedSchema inclusion in state-change DTOs, controller pipe application, query DTO consistency. Run after adding/modifying DTOs or controller endpoints.
testing
Verifies cross-feature workflow E2E test coverage against critical-workflows.md checklist. Checks WF-01~WF-35 coverage, step completeness, role correctness, side-effect verification, and status transition assertions. Run after adding workflow tests or before PR.
testing
SSOT(Single Source of Truth) 임포트 소스를 검증합니다. 타입/enum/상수가 올바른 패키지에서 임포트되는지 확인. 타입/enum 추가/수정 후 사용.
development
Verifies SQL safety — LIKE wildcard escaping, N+1 query pattern detection, COUNT(DISTINCT) for fan-out JOINs, RBAC INNER JOIN enforcement. Run after adding/modifying search or list API endpoints.