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
54 changes: 45 additions & 9 deletions .agents/skills/ai-review-workflow/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ Before editing, read:

1. `CONVENTIONS.md`
2. `docs/ai-rules.md`
3. `docs/git-commit-convention.md` before committing
3. `docs/architecture.md` — 워크스페이스 경계·인증·API·WebView·알림 동작 지도
4. `docs/git-commit-convention.md` before committing

Keep scope tight. Do not touch frontend code while working on review workflow
formatting unless the user explicitly asks.
Expand Down Expand Up @@ -247,30 +248,54 @@ Procedure:
':(glob,exclude)**/generated/**/*.d.ts'
```

4. Review high-signal issues only:
4. **컨텍스트 수집 — diff만 보고 리뷰하지 않는다.** finding을 확정하기 전에 head 기준
실제 코드를 읽는다:
- `docs/architecture.md`를 먼저 읽고 관련 워크스페이스의 동작 계약을 파악한다.
- PR body의 의도와 `👀 Review Point`를 확인한다.
- 변경 파일은 hunk뿐 아니라 파일 전체를 읽는다: `git show <head>:<path>`.
- 변경된 export·hook·API·공용 type의 호출처를 추적한다:
`git grep -n '<symbol>' <head> -- web app admin maeil-mail shared`.
- 같은 워크스페이스·도메인의 이웃 구현 1~2개와 비교한다. 다른 워크스페이스의
우연히 같은 이름인 구현을 표준으로 오인하지 않는다.
- 이전 AI 리뷰가 있으면 답글과 resolve 상태를 읽고 반박된 finding을 반복하지 않는다.

5. Review high-signal issues only:
- Comment only on likely production issues, security problems, data
corruption risks, broken user flows, or severe maintainability risks.
- Do not comment on generated files, style nits, formatting, subjective
naming, unchanged code, or speculative refactors.
- Only report issues introduced by the PR diff.

5. Decide comment placement:
- **Repository-specific high-signal areas** (근거는 `docs/architecture.md`와 실제 코드):
- `shared` fetcher·generated API·theme·WebView union 변경 후 소비처 누락.
- WebView message 송수신 한쪽만 변경, 브라우저 optional guard 제거, 로그인 분기 파손.
- cookie 인증을 bearer/local storage로 잘못 전제하거나 최초 profile probe를 상시
route guard로 오인한 변경.
- mutation invalidation 누락, 워크스페이스별 retry/cache 정책 혼동, 공용 `ApiError`
대신 axios 오류 shape 전제.
- FCM의 memberId/deviceUuid/token 연결, notificationType→route mapping, listener cleanup
중 하나가 빠진 변경.
- admin 외부 adapter를 공용 API로 오인하거나 Supabase lazy 초기화를 깨는 변경.
- **제네릭 필터**: 아무 React/React Native 모노레포에도 성립하는 코멘트면 버리거나,
실제 호출 흐름·워크스페이스 계약·file:line 근거를 붙여 서비스 특화 finding으로
다시 쓴다. 일반론 체크리스트는 게시하지 않는다.

6. Decide comment placement:
- `critical` and `major`: inline comments when the path and changed line are
valid.
- `minor`: summary only.
- If a finding cannot be tied to a valid changed line, keep it in the
summary instead of forcing an inline comment.
- **항상 PR 코멘트(리뷰)를 게시한다.** Findings가 0개여도 publisher가
`> ✅ 확실하게 수정이 필요한 항목을 찾지 못했습니다.` summary를 자동으로
채우므로, 스킬이 발동되면 무조건 step 7의 publisher 실행까지 진행한다.
채우므로, 스킬이 발동되면 무조건 step 8의 publisher 실행까지 진행한다.
"찾은 게 없으니 안 올리고 끝"은 금지.

6. Write the review result to the scratch `$REVIEW_JSON` (step 0, 레포 밖) using
7. Write the review result to the scratch `$REVIEW_JSON` (step 0, 레포 밖) using
`.github/codex-review-output-schema.json`. **레포 루트에 쓰지 않는다.** The
shared publisher owns the Preferred Review Body Format, Preferred Inline
Comment Format, `<!-- CODEX_REVIEW_COMMENT -->`, and `<!-- REVIEW_META ... -->`.

7. **반드시 publisher를 실행해 PR 코멘트를 게시한다.** 권장 경로는 `REVIEWER=claude`로
8. **반드시 publisher를 실행해 PR 코멘트를 게시한다.** 권장 경로는 `REVIEWER=claude`로
로컬 publisher를 직접 실행하는 것이다 (`REVIEW_JSON`은 step 0의 scratch 경로):

```bash
Expand Down Expand Up @@ -300,12 +325,12 @@ Procedure:
`OPENAI_API_KEY`, and must only decode the local structured review result
and run `.github/scripts/publish-codex-review.sh`.

8. If inline publishing fails because of invalid positions or paths, retry once
9. If inline publishing fails because of invalid positions or paths, retry once
with `comments: []` so the summary is still posted. The shared publisher
already performs this fallback; tell the user which inline comments could not
be attached if the logs reveal that detail.

9. **마무리: 워킹트리를 clean 상태로 남긴다.** 게시 후 `git status --short`가
10. **마무리: 워킹트리를 clean 상태로 남긴다.** 게시 후 `git status --short`가
비어 있어야 한다. 리뷰용 산출물이 워킹트리에 남았다면 삭제한다. 이 스킬은
커밋·브랜치·PR을 만들지 않는다 — 클라우드 라우틴에서 auto-PR로 쓸려 들어가는
것을 막기 위함이다.
Expand All @@ -314,6 +339,17 @@ Never publish mock findings as a real PR review. Never publish `APPROVE` or
`REQUEST_CHANGES`. Avoid posting a duplicate review for the same head commit
unless the user explicitly asks.

## Architecture Learning Loop

1. finding이 사람 답글로 반박·정정되거나 다른 리뷰어가 놓친 실제 이슈를 찾으면,
재현 근거가 있는 항목만 `.review-learnings/REVIEW.md`에 다음 리뷰 규칙으로 적립한다.
2. 실제 회귀가 확인된 영역만 repository-specific high-signal 목록에 추가한다. 일반론으로
목록을 희석하지 않는다.
3. 리뷰 중 `docs/architecture.md`와 코드가 어긋나면 코드를 기준으로 리뷰하고 사용자에게
architecture 문서 갱신을 별도 제안한다.
4. `<!-- REVIEW_META -->` finding id와 `<!-- CODEX_REVIEW_COMMENT -->` 마커는 과거 finding
추적 키이므로 유지한다.

## Generated File Exclusions

Keep both prompt diff exclusions and review policy aligned:
Expand Down
1 change: 1 addition & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"jsxSingleQuote": false,
"trailingComma": "all",
"singleAttributePerLine": false,
"embeddedLanguageFormatting": "off",
"proseWrap": "preserve",
"printWidth": 80,
"tabWidth": 2,
Expand Down
13 changes: 9 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,11 @@ AI agents MUST treat linting as a BLOCKING requirement.

Before considering any task complete, the AI MUST:

1. Run ESLint auto-fix:
1. Run sequential ESLint → Stylelint auto-fix:
- `pnpm --filter {workspace} lint:fix`
2. Run Stylelint auto-fix:
- `pnpm --filter {workspace} stylelint:fix`
3. Re-run lint checks:
- In workspaces with Stylelint, this script MUST run ESLint first and
Stylelint second. The two fixers MUST NOT run in parallel.
2. Re-run lint checks:
- ESLint / Stylelint MUST pass with ZERO errors

Rules:
Expand Down Expand Up @@ -177,6 +177,11 @@ These rules apply to commits and pull requests in **any** package.

When Codex reviews a pull request, it MUST act as a high-signal reviewer.

Before reviewing, read `docs/architecture.md`. It is the repository behavior map
for workspace boundaries, authentication, API, routing, WebView, and notification
contracts. If the document and code disagree, the code is authoritative and the
reviewer MUST propose updating the document.

Focus on:

- Production bugs, runtime crashes, and broken user flows
Expand Down
5 changes: 2 additions & 3 deletions admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint src --ext .ts,.tsx,.js,.jsx",
"lint:fix": "eslint src --ext .ts,.tsx,.js,.jsx --fix",
"stylelint:fix": "stylelint './src/**/*.tsx' --fix",
"format": "prettier --write .",
"lint:fix": "eslint src --ext .ts,.tsx,.js,.jsx --fix && stylelint './src/**/*.tsx' --fix",
"format": "prettier --write . && stylelint './src/**/*.tsx' --fix",
"type-check": "tsc --noEmit",
"generate-openapi-types": "dotenv -e .env -- bash -c 'openapi-typescript $VITE_OPEN_API_DOCS -o ./src/types/openapi.d.ts && prettier --write ./src/types/openapi.d.ts'"
},
Expand Down
21 changes: 12 additions & 9 deletions admin/src/components/reviewers/ReviewerModals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,31 +145,32 @@ export const AddReviewerModal = ({ onClose }: { onClose: () => void }) => {

const Overlay = styled.div`
position: fixed;
inset: 0;
z-index: 100;

display: flex;
align-items: center;
justify-content: center;

background: rgba(17, 24, 39, 0.4);
background: rgb(17 24 39 / 40%);

inset: 0;
`;

const ModalCard = styled.div`
width: 400px;
padding: ${({ theme }) => theme.spacing.lg};
border-radius: ${({ theme }) => theme.borderRadius.lg};
box-shadow: ${({ theme }) => theme.shadows.lg};

background: ${({ theme }) => theme.colors.white};
box-shadow: ${({ theme }) => theme.shadows.lg};
`;

const ModalTitle = styled.h2`
margin-bottom: ${({ theme }) => theme.spacing.md};

color: ${({ theme }) => theme.colors.gray900};
font-size: ${({ theme }) => theme.fontSize.lg};
font-weight: ${({ theme }) => theme.fontWeight.semibold};
font-size: ${({ theme }) => theme.fontSize.lg};
`;

const Field = styled.div`
Expand All @@ -182,8 +183,8 @@ const Field = styled.div`

const FieldLabel = styled.label`
color: ${({ theme }) => theme.colors.gray700};
font-size: ${({ theme }) => theme.fontSize.sm};
font-weight: ${({ theme }) => theme.fontWeight.medium};
font-size: ${({ theme }) => theme.fontSize.sm};
`;

const FieldInput = styled.input`
Expand Down Expand Up @@ -227,17 +228,18 @@ const PrimaryButton = styled.button`

background: ${({ theme }) => theme.colors.primary};
color: ${({ theme }) => theme.colors.white};
cursor: pointer;
font-size: ${({ theme }) => theme.fontSize.sm};
font-weight: ${({ theme }) => theme.fontWeight.semibold};
font-size: ${({ theme }) => theme.fontSize.sm};

cursor: pointer;

&:hover {
background: ${({ theme }) => theme.colors.primaryHover};
}

&:disabled {
opacity: 0.5;
cursor: not-allowed;
opacity: 0.5;
}
`;

Expand All @@ -248,9 +250,10 @@ const SecondaryButton = styled.button`

background: ${({ theme }) => theme.colors.white};
color: ${({ theme }) => theme.colors.gray600};
cursor: pointer;
font-size: ${({ theme }) => theme.fontSize.sm};

cursor: pointer;

&:hover {
background: ${({ theme }) => theme.colors.gray50};
}
Expand Down
25 changes: 14 additions & 11 deletions admin/src/components/reviewers/ReviewersTableBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,8 @@ const Td = styled.td`
text-align: left;

&.num {
font-variant-numeric: tabular-nums;
text-align: right;
font-variant-numeric: tabular-nums;
}

&.strong {
Expand All @@ -256,12 +256,11 @@ const GaugeCell = styled.div`
`;

const GaugeTrack = styled.div`
overflow: hidden;
width: 80px;
height: 6px;
border-radius: ${({ theme }) => theme.borderRadius.full};

overflow: hidden;

background: ${({ theme }) => theme.colors.gray100};
`;

Expand All @@ -275,6 +274,7 @@ const GaugeFill = styled.div`
const GaugeText = styled.span`
color: ${({ theme }) => theme.colors.gray500};
font-size: ${({ theme }) => theme.fontSize.xs};

font-variant-numeric: tabular-nums;
`;

Expand All @@ -289,8 +289,8 @@ const StateBadge = styled.span<{ $vacation: boolean }>`

background: ${({ $vacation }) => ($vacation ? '#FEF3C7' : '#D1FAE5')};
color: ${({ $vacation }) => ($vacation ? '#92400E' : '#065F46')};
font-size: ${({ theme }) => theme.fontSize.xs};
font-weight: ${({ theme }) => theme.fontWeight.semibold};
font-size: ${({ theme }) => theme.fontSize.xs};
`;

const SubCount = styled.span`
Expand Down Expand Up @@ -329,17 +329,18 @@ const MiniButton = styled.button`

background: ${({ theme }) => theme.colors.white};
color: ${({ theme }) => theme.colors.gray600};
cursor: pointer;
font-size: ${({ theme }) => theme.fontSize.xs};
white-space: nowrap;

cursor: pointer;

&:hover {
background: ${({ theme }) => theme.colors.gray50};
}

&:disabled {
opacity: 0.5;
cursor: not-allowed;
opacity: 0.5;
}
`;

Expand All @@ -354,15 +355,16 @@ const DeleteButton = styled.button<{ $confirming: boolean }>`
$confirming ? '#FEE2E2' : theme.colors.white};
color: ${({ theme, $confirming }) =>
$confirming ? theme.colors.error : theme.colors.gray600};
cursor: pointer;
font-size: ${({ theme }) => theme.fontSize.xs};
font-weight: ${({ theme, $confirming }) =>
$confirming ? theme.fontWeight.semibold : theme.fontWeight.normal};
font-size: ${({ theme }) => theme.fontSize.xs};
white-space: nowrap;

cursor: pointer;

&:disabled {
opacity: 0.5;
cursor: not-allowed;
opacity: 0.5;
}
`;

Expand All @@ -373,16 +375,17 @@ const ToggleButton = styled.button`

background: ${({ theme }) => theme.colors.white};
color: ${({ theme }) => theme.colors.gray600};
cursor: pointer;
font-size: ${({ theme }) => theme.fontSize.xs};

cursor: pointer;

&:hover {
background: ${({ theme }) => theme.colors.gray50};
}

&:disabled {
opacity: 0.5;
cursor: not-allowed;
opacity: 0.5;
}
`;

Expand Down
11 changes: 6 additions & 5 deletions admin/src/components/reviewers/SummaryStatCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ export const SummaryStatCard = ({ label, value, emphasis }: Props) => (

export const SummaryStatCardRow = styled.div`
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: ${({ theme }) => theme.spacing.md};

grid-template-columns: repeat(4, 1fr);
`;

export const SummaryStatCardSkeleton = () => (
Expand All @@ -35,17 +36,17 @@ const Card = styled.div<{ $emphasis?: 'default' | 'error' }>`
${({ theme, $emphasis }) =>
$emphasis === 'error' ? theme.colors.error : theme.colors.primary};
border-radius: ${({ theme }) => theme.borderRadius.lg};
box-shadow: ${({ theme }) => theme.shadows.sm};

background: ${({ theme }) => theme.colors.white};
box-shadow: ${({ theme }) => theme.shadows.sm};
`;

const Label = styled.span`
display: block;

color: ${({ theme }) => theme.colors.gray500};
font-size: ${({ theme }) => theme.fontSize.xs};
font-weight: ${({ theme }) => theme.fontWeight.medium};
font-size: ${({ theme }) => theme.fontSize.xs};
letter-spacing: 0.04em;
`;

Expand All @@ -56,13 +57,13 @@ const Value = styled.span<{ $emphasis?: 'default' | 'error' }>`

color: ${({ theme, $emphasis }) =>
$emphasis === 'error' ? theme.colors.error : theme.colors.gray900};
font-size: 28px;
font-weight: ${({ theme }) => theme.fontWeight.bold};
font-size: 28px;

font-variant-numeric: tabular-nums;
`;

const SkeletonBlock = styled.div`
border-radius: ${({ theme }) => theme.borderRadius.sm};

background: ${({ theme }) => theme.colors.gray100};
`;
Loading
Loading