feat: Stage 2 TypeScript 타입 안전성 개선 - any 타입 83개 → 62개 대폭 감소
✨ 주요 개선사항: - any 타입 83개에서 62개로 21개 수정 (25% 감소) - 모든 ESLint 에러 11개 → 0개 완전 해결 - 타입 안전성 대폭 향상으로 런타임 오류 가능성 감소 🔧 수정된 파일들: • PWADebug.tsx - 사용하지 않는 import들에 _ prefix 추가 • categoryUtils.ts - 불필요한 any 캐스트 제거 • TransactionsHeader.tsx - BudgetData 인터페이스 정의 • storageUtils.ts - generic 타입과 unknown 타입 적용 • 각종 error handler들 - Error | {message?: string} 타입 적용 • test 파일들 - 적절한 mock 인터페이스 정의 • 유틸리티 파일들 - any → unknown 또는 적절한 타입으로 교체 🏆 성과: - 코드 품질 크게 향상 (280 → 80 문제로 71% 감소) - TypeScript 컴파일러의 타입 체크 효과성 증대 - 개발자 경험 개선 (IDE 자동완성, 타입 추론 등) 🧹 추가 정리: - ESLint no-console/no-alert 경고 해결 - Prettier 포맷팅 적용으로 코드 스타일 통일 🎯 다음 단계: 남은 62개 any 타입 계속 개선 예정 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
692
docs/linear-integration-guide.md
Normal file
692
docs/linear-integration-guide.md
Normal file
@@ -0,0 +1,692 @@
|
||||
# Linear 프로젝트 관리 도구 연동 가이드
|
||||
|
||||
## 개요
|
||||
|
||||
이 가이드는 Zellyy Finance 프로젝트에 Linear.app 프로젝트 관리 도구를 완전히 연동하는 방법을 설명합니다. Linear와 GitHub의 양방향 동기화, 자동화된 워크플로우, 실시간 리포팅 시스템 구축을 다룹니다.
|
||||
|
||||
## 목차
|
||||
|
||||
1. [Linear 계정 및 프로젝트 설정](#1-linear-계정-및-프로젝트-설정)
|
||||
2. [GitHub 연동 설정](#2-github-연동-설정)
|
||||
3. [워크플로우 자동화](#3-워크플로우-자동화)
|
||||
4. [릴리즈 관리 시스템](#4-릴리즈-관리-시스템)
|
||||
5. [팀 협업 도구](#5-팀-협업-도구)
|
||||
6. [리포팅 대시보드](#6-리포팅-대시보드)
|
||||
|
||||
## 1. Linear 계정 및 프로젝트 설정
|
||||
|
||||
### 1.1 Linear 워크스페이스 생성
|
||||
|
||||
1. [Linear.app](https://linear.app) 접속
|
||||
2. "Create workspace" 클릭
|
||||
3. 워크스페이스 정보 입력:
|
||||
- Workspace name: `Zellyy Finance`
|
||||
- URL: `zellyy-finance`
|
||||
- Plan: Professional (권장)
|
||||
|
||||
### 1.2 프로젝트 구조 설정
|
||||
|
||||
```yaml
|
||||
# 프로젝트 구조
|
||||
Zellyy Finance/
|
||||
├── Teams/
|
||||
│ ├── Frontend
|
||||
│ ├── Backend
|
||||
│ └── DevOps
|
||||
├── Projects/
|
||||
│ ├── Web App
|
||||
│ ├── Mobile App
|
||||
│ └── Infrastructure
|
||||
└── Roadmap/
|
||||
├── Q1 2025
|
||||
├── Q2 2025
|
||||
└── Q3 2025
|
||||
```
|
||||
|
||||
### 1.3 이슈 타입 및 라벨 체계
|
||||
|
||||
#### 이슈 타입
|
||||
- **Epic**: 대규모 기능 그룹
|
||||
- **Feature**: 새로운 기능
|
||||
- **Bug**: 버그 수정
|
||||
- **Task**: 일반 작업
|
||||
- **Improvement**: 개선 사항
|
||||
|
||||
#### 라벨 체계
|
||||
```yaml
|
||||
Priority:
|
||||
- P0: Critical
|
||||
- P1: High
|
||||
- P2: Medium
|
||||
- P3: Low
|
||||
|
||||
Type:
|
||||
- frontend
|
||||
- backend
|
||||
- mobile
|
||||
- devops
|
||||
- security
|
||||
- performance
|
||||
|
||||
Status:
|
||||
- backlog
|
||||
- todo
|
||||
- in-progress
|
||||
- review
|
||||
- done
|
||||
- cancelled
|
||||
```
|
||||
|
||||
### 1.4 워크플로우 상태 정의
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Backlog] --> B[Todo]
|
||||
B --> C[In Progress]
|
||||
C --> D[In Review]
|
||||
D --> E[Done]
|
||||
C --> F[Blocked]
|
||||
F --> C
|
||||
B --> G[Cancelled]
|
||||
```
|
||||
|
||||
## 2. GitHub 연동 설정
|
||||
|
||||
### 2.1 Linear GitHub 앱 설치
|
||||
|
||||
1. Linear 설정 → Integrations → GitHub
|
||||
2. "Install GitHub App" 클릭
|
||||
3. 권한 승인:
|
||||
- Repository access: `zellyy-finance`
|
||||
- Permissions: Read & Write
|
||||
|
||||
### 2.2 브랜치 명명 규칙
|
||||
|
||||
```bash
|
||||
# Linear 이슈 ID 기반 브랜치명
|
||||
feature/ZEL-123-user-authentication
|
||||
bugfix/ZEL-456-login-error
|
||||
task/ZEL-789-update-dependencies
|
||||
```
|
||||
|
||||
### 2.3 커밋 메시지 규칙
|
||||
|
||||
```bash
|
||||
# Linear 이슈 자동 연결
|
||||
git commit -m "feat: implement user authentication [ZEL-123]"
|
||||
git commit -m "fix: resolve login error (Fixes ZEL-456)"
|
||||
git commit -m "chore: update dependencies - ZEL-789"
|
||||
```
|
||||
|
||||
### 2.4 PR 템플릿 설정
|
||||
|
||||
`.github/pull_request_template.md`:
|
||||
```markdown
|
||||
## 개요
|
||||
<!-- PR 설명 -->
|
||||
|
||||
## Linear 이슈
|
||||
Closes ZEL-XXX
|
||||
|
||||
## 변경 사항
|
||||
- [ ] 기능 A 구현
|
||||
- [ ] 버그 B 수정
|
||||
- [ ] 테스트 추가
|
||||
|
||||
## 테스트
|
||||
- [ ] 유닛 테스트 통과
|
||||
- [ ] E2E 테스트 통과
|
||||
- [ ] 수동 테스트 완료
|
||||
|
||||
## 스크린샷
|
||||
<!-- 필요시 스크린샷 첨부 -->
|
||||
```
|
||||
|
||||
## 3. 워크플로우 자동화
|
||||
|
||||
### 3.1 Linear API 설정
|
||||
|
||||
1. Linear Settings → API → Personal API keys
|
||||
2. "Create key" 클릭
|
||||
3. Key name: `zellyy-finance-automation`
|
||||
4. 생성된 키를 GitHub Secrets에 저장:
|
||||
```bash
|
||||
LINEAR_API_KEY=lin_api_xxxxxxxxxxxxxxxx
|
||||
```
|
||||
|
||||
### 3.2 GitHub Actions 워크플로우
|
||||
|
||||
`.github/workflows/linear-integration.yml`:
|
||||
```yaml
|
||||
name: Linear Integration
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, closed, ready_for_review]
|
||||
issues:
|
||||
types: [opened, closed]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
env:
|
||||
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
|
||||
|
||||
jobs:
|
||||
sync-linear:
|
||||
name: Sync with Linear
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Extract Linear Issue ID
|
||||
id: linear-issue
|
||||
run: |
|
||||
# PR 제목/본문에서 Linear 이슈 ID 추출
|
||||
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||
ISSUE_ID=$(echo "${{ github.event.pull_request.title }} ${{ github.event.pull_request.body }}" | grep -oE 'ZEL-[0-9]+' | head -1)
|
||||
fi
|
||||
echo "issue_id=$ISSUE_ID" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Update Linear Issue Status
|
||||
if: steps.linear-issue.outputs.issue_id
|
||||
run: |
|
||||
node scripts/linear-sync.js \
|
||||
--issue-id="${{ steps.linear-issue.outputs.issue_id }}" \
|
||||
--event="${{ github.event_name }}" \
|
||||
--action="${{ github.event.action }}"
|
||||
|
||||
- name: Create Linear Comment
|
||||
if: github.event_name == 'pull_request' && github.event.action == 'opened'
|
||||
run: |
|
||||
node scripts/linear-comment.js \
|
||||
--issue-id="${{ steps.linear-issue.outputs.issue_id }}" \
|
||||
--pr-url="${{ github.event.pull_request.html_url }}" \
|
||||
--pr-author="${{ github.event.pull_request.user.login }}"
|
||||
```
|
||||
|
||||
### 3.3 Linear 동기화 스크립트
|
||||
|
||||
`scripts/linear-sync.js`:
|
||||
```javascript
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { LinearClient } = require('@linear/sdk');
|
||||
const { program } = require('commander');
|
||||
|
||||
// Linear 클라이언트 초기화
|
||||
const linear = new LinearClient({
|
||||
apiKey: process.env.LINEAR_API_KEY
|
||||
});
|
||||
|
||||
program
|
||||
.option('--issue-id <id>', 'Linear issue ID')
|
||||
.option('--event <event>', 'GitHub event type')
|
||||
.option('--action <action>', 'GitHub action type')
|
||||
.parse();
|
||||
|
||||
const options = program.opts();
|
||||
|
||||
async function updateIssueStatus() {
|
||||
try {
|
||||
// 이슈 조회
|
||||
const issue = await linear.issue(options.issueId);
|
||||
|
||||
if (!issue) {
|
||||
console.error(`Issue ${options.issueId} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
let stateId;
|
||||
|
||||
// 이벤트에 따른 상태 결정
|
||||
if (options.event === 'pull_request') {
|
||||
switch (options.action) {
|
||||
case 'opened':
|
||||
stateId = await getStateId('In Progress');
|
||||
break;
|
||||
case 'ready_for_review':
|
||||
stateId = await getStateId('In Review');
|
||||
break;
|
||||
case 'closed':
|
||||
if (process.env.GITHUB_PR_MERGED === 'true') {
|
||||
stateId = await getStateId('Done');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 상태 업데이트
|
||||
if (stateId) {
|
||||
await issue.update({ stateId });
|
||||
console.log(`Updated ${options.issueId} status`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to update Linear issue:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function getStateId(stateName) {
|
||||
const states = await linear.workflowStates();
|
||||
const state = states.nodes.find(s => s.name === stateName);
|
||||
return state?.id;
|
||||
}
|
||||
|
||||
updateIssueStatus();
|
||||
```
|
||||
|
||||
## 4. 릴리즈 관리 시스템
|
||||
|
||||
### 4.1 Semantic Release 연동
|
||||
|
||||
`.releaserc.json` 업데이트:
|
||||
```json
|
||||
{
|
||||
"branches": ["main"],
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer",
|
||||
"@semantic-release/release-notes-generator",
|
||||
[
|
||||
"@semantic-release/exec",
|
||||
{
|
||||
"prepareCmd": "node scripts/linear-release-prep.js ${nextRelease.version}",
|
||||
"successCmd": "node scripts/linear-release-complete.js ${nextRelease.version}"
|
||||
}
|
||||
],
|
||||
"@semantic-release/changelog",
|
||||
"@semantic-release/npm",
|
||||
"@semantic-release/github",
|
||||
"@semantic-release/git"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 릴리즈 준비 스크립트
|
||||
|
||||
`scripts/linear-release-prep.js`:
|
||||
```javascript
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { LinearClient } = require('@linear/sdk');
|
||||
const fs = require('fs');
|
||||
|
||||
const linear = new LinearClient({
|
||||
apiKey: process.env.LINEAR_API_KEY
|
||||
});
|
||||
|
||||
const version = process.argv[2];
|
||||
|
||||
async function prepareRelease() {
|
||||
try {
|
||||
// 완료된 이슈 조회
|
||||
const issues = await linear.issues({
|
||||
filter: {
|
||||
state: { name: { eq: "Done" } },
|
||||
project: { name: { eq: "Zellyy Finance" } }
|
||||
}
|
||||
});
|
||||
|
||||
// 릴리즈 노트 생성
|
||||
const releaseNotes = {
|
||||
version,
|
||||
date: new Date().toISOString(),
|
||||
issues: issues.nodes.map(issue => ({
|
||||
id: issue.identifier,
|
||||
title: issue.title,
|
||||
type: issue.labels.nodes[0]?.name || 'task',
|
||||
url: issue.url
|
||||
}))
|
||||
};
|
||||
|
||||
// 릴리즈 노트 파일 저장
|
||||
fs.writeFileSync(
|
||||
`releases/v${version}.json`,
|
||||
JSON.stringify(releaseNotes, null, 2)
|
||||
);
|
||||
|
||||
console.log(`Prepared release notes for v${version}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to prepare release:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
prepareRelease();
|
||||
```
|
||||
|
||||
## 5. 팀 협업 도구
|
||||
|
||||
### 5.1 Slack 연동 설정
|
||||
|
||||
1. Linear Settings → Integrations → Slack
|
||||
2. "Connect Slack" 클릭
|
||||
3. 채널 매핑:
|
||||
- `#dev-frontend` → Frontend team
|
||||
- `#dev-backend` → Backend team
|
||||
- `#dev-mobile` → Mobile team
|
||||
|
||||
### 5.2 Slack 알림 규칙
|
||||
|
||||
```yaml
|
||||
알림 트리거:
|
||||
- 이슈 생성: 담당 팀 채널
|
||||
- 이슈 할당: 담당자 DM
|
||||
- 상태 변경: 관련 채널
|
||||
- 코멘트 추가: 멘션된 사용자
|
||||
- 우선순위 변경: P0/P1만 전체 알림
|
||||
```
|
||||
|
||||
### 5.3 일일 스탠드업 자동화
|
||||
|
||||
`scripts/daily-standup.js`:
|
||||
```javascript
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { LinearClient } = require('@linear/sdk');
|
||||
const { WebClient } = require('@slack/web-api');
|
||||
|
||||
const linear = new LinearClient({
|
||||
apiKey: process.env.LINEAR_API_KEY
|
||||
});
|
||||
|
||||
const slack = new WebClient(process.env.SLACK_BOT_TOKEN);
|
||||
|
||||
async function generateStandup() {
|
||||
const teams = ['Frontend', 'Backend', 'Mobile'];
|
||||
|
||||
for (const team of teams) {
|
||||
// 어제 완료된 이슈
|
||||
const completed = await getIssues(team, 'Done', 1);
|
||||
|
||||
// 오늘 진행중인 이슈
|
||||
const inProgress = await getIssues(team, 'In Progress');
|
||||
|
||||
// 블로커
|
||||
const blocked = await getIssues(team, 'Blocked');
|
||||
|
||||
const message = formatStandupMessage(team, {
|
||||
completed,
|
||||
inProgress,
|
||||
blocked
|
||||
});
|
||||
|
||||
await postToSlack(team, message);
|
||||
}
|
||||
}
|
||||
|
||||
async function getIssues(team, state, daysAgo = 0) {
|
||||
const filter = {
|
||||
team: { name: { eq: team } },
|
||||
state: { name: { eq: state } }
|
||||
};
|
||||
|
||||
if (daysAgo > 0) {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - daysAgo);
|
||||
filter.updatedAt = { gte: date.toISOString() };
|
||||
}
|
||||
|
||||
const issues = await linear.issues({ filter });
|
||||
return issues.nodes;
|
||||
}
|
||||
|
||||
function formatStandupMessage(team, data) {
|
||||
return {
|
||||
blocks: [
|
||||
{
|
||||
type: "header",
|
||||
text: {
|
||||
type: "plain_text",
|
||||
text: `${team} Team 일일 스탠드업 📋`
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `*어제 완료* ✅\n${formatIssues(data.completed)}`
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `*오늘 진행* 🚀\n${formatIssues(data.inProgress)}`
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `*블로커* 🚨\n${formatIssues(data.blocked)}`
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
// 매일 오전 9시 실행
|
||||
generateStandup();
|
||||
```
|
||||
|
||||
## 6. 리포팅 대시보드
|
||||
|
||||
### 6.1 프로젝트 메트릭 수집
|
||||
|
||||
`scripts/linear-metrics.js`:
|
||||
```javascript
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { LinearClient } = require('@linear/sdk');
|
||||
const fs = require('fs');
|
||||
|
||||
const linear = new LinearClient({
|
||||
apiKey: process.env.LINEAR_API_KEY
|
||||
});
|
||||
|
||||
async function collectMetrics() {
|
||||
const metrics = {
|
||||
timestamp: new Date().toISOString(),
|
||||
teams: {},
|
||||
overall: {
|
||||
totalIssues: 0,
|
||||
completedIssues: 0,
|
||||
averageLeadTime: 0,
|
||||
velocity: 0
|
||||
}
|
||||
};
|
||||
|
||||
// 팀별 메트릭 수집
|
||||
const teams = await linear.teams();
|
||||
|
||||
for (const team of teams.nodes) {
|
||||
const teamMetrics = await getTeamMetrics(team.id);
|
||||
metrics.teams[team.name] = teamMetrics;
|
||||
|
||||
// 전체 메트릭 집계
|
||||
metrics.overall.totalIssues += teamMetrics.totalIssues;
|
||||
metrics.overall.completedIssues += teamMetrics.completedIssues;
|
||||
}
|
||||
|
||||
// 메트릭 저장
|
||||
const filename = `metrics/linear-${new Date().toISOString().split('T')[0]}.json`;
|
||||
fs.writeFileSync(filename, JSON.stringify(metrics, null, 2));
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
async function getTeamMetrics(teamId) {
|
||||
// 이번 주 이슈들
|
||||
const weekStart = new Date();
|
||||
weekStart.setDate(weekStart.getDate() - 7);
|
||||
|
||||
const issues = await linear.issues({
|
||||
filter: {
|
||||
team: { id: { eq: teamId } },
|
||||
createdAt: { gte: weekStart.toISOString() }
|
||||
}
|
||||
});
|
||||
|
||||
const completed = issues.nodes.filter(i => i.state.name === 'Done');
|
||||
const leadTimes = completed.map(i => calculateLeadTime(i));
|
||||
|
||||
return {
|
||||
totalIssues: issues.nodes.length,
|
||||
completedIssues: completed.length,
|
||||
averageLeadTime: average(leadTimes),
|
||||
byPriority: groupByPriority(issues.nodes),
|
||||
byType: groupByType(issues.nodes)
|
||||
};
|
||||
}
|
||||
|
||||
function calculateLeadTime(issue) {
|
||||
const created = new Date(issue.createdAt);
|
||||
const completed = new Date(issue.completedAt);
|
||||
return (completed - created) / (1000 * 60 * 60); // hours
|
||||
}
|
||||
|
||||
collectMetrics();
|
||||
```
|
||||
|
||||
### 6.2 React 대시보드 컴포넌트
|
||||
|
||||
`src/components/linear/Dashboard.tsx`:
|
||||
```typescript
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { BarChart, LineChart, PieChart } from 'recharts';
|
||||
import { useLinearMetrics } from '@/hooks/useLinearMetrics';
|
||||
|
||||
export function LinearDashboard() {
|
||||
const { metrics, loading, error } = useLinearMetrics();
|
||||
|
||||
if (loading) return <div>Loading metrics...</div>;
|
||||
if (error) return <div>Error loading metrics</div>;
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{/* 완료율 카드 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>완료율</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-3xl font-bold">
|
||||
{((metrics.overall.completedIssues / metrics.overall.totalIssues) * 100).toFixed(1)}%
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{metrics.overall.completedIssues} / {metrics.overall.totalIssues} 이슈
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 평균 리드타임 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>평균 리드타임</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-3xl font-bold">
|
||||
{metrics.overall.averageLeadTime.toFixed(1)}h
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
생성에서 완료까지
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 팀별 진행률 차트 */}
|
||||
<Card className="col-span-full">
|
||||
<CardHeader>
|
||||
<CardTitle>팀별 진행률</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<TeamProgressChart data={metrics.teams} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 번다운 차트 */}
|
||||
<Card className="col-span-2">
|
||||
<CardHeader>
|
||||
<CardTitle>스프린트 번다운</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<BurndownChart data={metrics.burndown} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 이슈 타입별 분포 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>이슈 타입 분포</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<IssueTypeChart data={metrics.issueTypes} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 설정 체크리스트
|
||||
|
||||
### 초기 설정
|
||||
- [ ] Linear 워크스페이스 생성
|
||||
- [ ] 팀 및 프로젝트 구조 설정
|
||||
- [ ] 라벨 및 워크플로우 정의
|
||||
- [ ] GitHub 앱 연동
|
||||
|
||||
### 자동화 구축
|
||||
- [ ] Linear API 키 생성
|
||||
- [ ] GitHub Actions 워크플로우 구현
|
||||
- [ ] 동기화 스크립트 배포
|
||||
- [ ] 릴리즈 자동화 설정
|
||||
|
||||
### 팀 협업
|
||||
- [ ] Slack 연동 설정
|
||||
- [ ] 알림 규칙 구성
|
||||
- [ ] 일일 스탠드업 자동화
|
||||
- [ ] 팀 템플릿 생성
|
||||
|
||||
### 리포팅
|
||||
- [ ] 메트릭 수집 스케줄러 설정
|
||||
- [ ] 대시보드 컴포넌트 구현
|
||||
- [ ] 리포트 자동 생성 설정
|
||||
- [ ] 데이터 시각화 구현
|
||||
|
||||
## 문제 해결
|
||||
|
||||
### Linear API 연결 실패
|
||||
```bash
|
||||
# API 키 확인
|
||||
echo $LINEAR_API_KEY
|
||||
|
||||
# 권한 확인
|
||||
curl -H "Authorization: $LINEAR_API_KEY" \
|
||||
https://api.linear.app/graphql \
|
||||
-d '{"query":"{ viewer { id email }}"}'
|
||||
```
|
||||
|
||||
### GitHub 동기화 문제
|
||||
- GitHub 앱 권한 재확인
|
||||
- Webhook 전송 로그 확인
|
||||
- Linear 이슈 ID 형식 검증
|
||||
|
||||
### Slack 알림 미작동
|
||||
- Slack 봇 토큰 유효성 확인
|
||||
- 채널 권한 설정 확인
|
||||
- 알림 필터 규칙 검토
|
||||
|
||||
---
|
||||
|
||||
이 가이드는 Zellyy Finance 프로젝트의 Linear 통합을 위한 완전한 참조 문서입니다.
|
||||
Reference in New Issue
Block a user