sleepcode 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +178 -0
- package/bin/index.js +331 -0
- package/package.json +17 -0
- package/templates/common/README.md +97 -0
- package/templates/common/ai_worker.sh +23 -0
- package/templates/common/log_filter.py +63 -0
- package/templates/common/run_forever.sh +59 -0
- package/templates/rules/custom.md +69 -0
- package/templates/rules/nextjs.md +77 -0
- package/templates/rules/react-native.md +76 -0
- package/templates/rules/spring-boot.md +78 -0
- package/templates/settings/custom.json +19 -0
- package/templates/settings/nextjs.json +30 -0
- package/templates/settings/react-native.json +30 -0
- package/templates/settings/spring-boot.json +24 -0
package/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# SleepCode
|
|
2
|
+
|
|
3
|
+
**AI codes while you sleep** — Claude AI 야간 자동화 세팅 CLI
|
|
4
|
+
|
|
5
|
+
잠자는 동안 AI가 코드를 작성하고, 빌드하고, 테스트하고, 커밋합니다.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 설치
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g sleepcode
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
또는 npx로 바로 실행:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx sleepcode
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 빠른 시작
|
|
24
|
+
|
|
25
|
+
### 1. 프로젝트 루트에서 실행
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cd my-project
|
|
29
|
+
npx sleepcode
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
인터랙티브 모드로 프로젝트 타입, 이름, AI 역할 등을 설정합니다.
|
|
33
|
+
|
|
34
|
+
### 2. 태스크 작성
|
|
35
|
+
|
|
36
|
+
`.ai/tasks.md` 에 AI가 수행할 작업을 작성합니다:
|
|
37
|
+
|
|
38
|
+
```markdown
|
|
39
|
+
# 작업 목록
|
|
40
|
+
|
|
41
|
+
- [ ] 로그인 화면 구현
|
|
42
|
+
- [ ] 회원가입 API 연동
|
|
43
|
+
- [ ] 홈 화면 UI 개선
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 3. 실행
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# 1회 실행
|
|
50
|
+
./.ai/ai_worker.sh
|
|
51
|
+
|
|
52
|
+
# 무한 루프 (tmux 권장)
|
|
53
|
+
tmux new -s ai './.ai/run_forever.sh'
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 4. 아침에 확인
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
git log --oneline --since="12 hours ago"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 지원 프로젝트 타입
|
|
65
|
+
|
|
66
|
+
| 타입 | 설명 |
|
|
67
|
+
|------|------|
|
|
68
|
+
| `spring-boot` | Spring Boot (Kotlin/Java) — Gradle 빌드/테스트 |
|
|
69
|
+
| `react-native` | React Native (TypeScript) — tsc 타입체크 |
|
|
70
|
+
| `nextjs` | Next.js (TypeScript) — npm build/test/lint |
|
|
71
|
+
| `custom` | 직접 설정 — 빌드/테스트/린트 명령어 수동 입력 |
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## CLI 옵션
|
|
76
|
+
|
|
77
|
+
인터랙티브 모드 외에 CLI 인자로도 사용 가능합니다:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npx sleepcode --type react-native --name my-app --role "쇼핑몰 앱 개발"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
| 옵션 | 설명 |
|
|
84
|
+
|------|------|
|
|
85
|
+
| `--type <type>` | 프로젝트 타입 (`spring-boot`, `react-native`, `nextjs`, `custom`) |
|
|
86
|
+
| `--name <name>` | 프로젝트 이름 |
|
|
87
|
+
| `--role <desc>` | AI 역할 설명 |
|
|
88
|
+
| `--figma-key <key>` | Figma API Key (선택) |
|
|
89
|
+
| `--interval <sec>` | 반복 간격 초 (기본: 30) |
|
|
90
|
+
| `-f, --force` | 기존 `.ai/` 폴더 덮어쓰기 |
|
|
91
|
+
| `-h, --help` | 도움말 |
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 생성되는 파일
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
.ai/
|
|
99
|
+
rules.md # AI 역할 + 작업 규칙 (프롬프트)
|
|
100
|
+
tasks.md # 작업 목록 (체크리스트)
|
|
101
|
+
docs/ # 참고 자료 (피그마 스크린샷, 기획서 등)
|
|
102
|
+
ai_worker.sh # 1회 실행 스크립트
|
|
103
|
+
run_forever.sh # 무한 루프 스크립트
|
|
104
|
+
log_filter.py # 실시간 로그 필터
|
|
105
|
+
logs/ # 실행 로그 (자동 생성)
|
|
106
|
+
README.md # 사용 가이드
|
|
107
|
+
|
|
108
|
+
.claude/
|
|
109
|
+
settings.local.json # Claude 권한 설정
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## 작동 원리
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
rules.md + tasks.md → 프롬프트 조합 → claude -p (비대화형) → 코드 작성 → git commit → 반복
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
1. `rules.md`(AI 역할/규칙)와 `tasks.md`(작업 목록)를 합쳐서 프롬프트로 전달
|
|
121
|
+
2. Claude가 태스크를 하나씩 수행 (코드 작성 → 빌드/테스트 → 오류 수정)
|
|
122
|
+
3. 태스크 완료 시 `[x]` 체크 + `git commit`
|
|
123
|
+
4. 모든 태스크 완료되면 자동 종료 (또는 대기 후 반복)
|
|
124
|
+
|
|
125
|
+
### 실시간 로그
|
|
126
|
+
|
|
127
|
+
`stream-json` 출력을 `log_filter.py`가 파싱하여 핵심 메시지만 표시합니다:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
[TEXT] 로그인 화면을 구현하겠습니다...
|
|
131
|
+
[TOOL] Edit: src/screens/LoginScreen.tsx
|
|
132
|
+
[TOOL] Bash: npx tsc --noEmit
|
|
133
|
+
[TEXT] 타입 체크 통과, 커밋합니다.
|
|
134
|
+
[TOOL] Bash: git commit -m "feat: 로그인 화면 구현"
|
|
135
|
+
[DONE] 완료
|
|
136
|
+
[COST] input: 50,000 / output: 12,000
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## tmux 관리
|
|
142
|
+
|
|
143
|
+
| 동작 | 명령어 |
|
|
144
|
+
|------|--------|
|
|
145
|
+
| 세션 생성 + 실행 | `tmux new -s ai './.ai/run_forever.sh'` |
|
|
146
|
+
| 백그라운드 전환 | `Ctrl+B` → `D` |
|
|
147
|
+
| 세션 재접속 | `tmux attach -t ai` |
|
|
148
|
+
| 실시간 로그 | `tail -f .ai/logs/worker_*.log` |
|
|
149
|
+
| 종료 | `tmux attach -t ai` → `Ctrl+C` |
|
|
150
|
+
| 세션 삭제 | `tmux kill-session -t ai` |
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 사전 준비
|
|
155
|
+
|
|
156
|
+
1. [Claude CLI](https://docs.anthropic.com/en/docs/claude-code) 설치
|
|
157
|
+
|
|
158
|
+
2. `--dangerously-skip-permissions` 최초 1회 동의:
|
|
159
|
+
```bash
|
|
160
|
+
claude --dangerously-skip-permissions
|
|
161
|
+
# 동의 프롬프트 수락 후 Ctrl+C
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## 커스터마이징
|
|
167
|
+
|
|
168
|
+
- **AI 역할/규칙 변경**: `.ai/rules.md` 수정
|
|
169
|
+
- **작업 목록 변경**: `.ai/tasks.md` 수정
|
|
170
|
+
- **참고 자료 추가**: `.ai/docs/`에 파일 추가 (스크린샷, 기획서 등)
|
|
171
|
+
- **반복 간격 변경**: `.ai/run_forever.sh`의 `sleep` 값 수정
|
|
172
|
+
- **Claude 권한 변경**: `.claude/settings.local.json` 수정
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
MIT
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const readline = require('readline');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
// ─── 색상 ───
|
|
8
|
+
const C = {
|
|
9
|
+
reset: '\x1b[0m',
|
|
10
|
+
bold: '\x1b[1m',
|
|
11
|
+
dim: '\x1b[2m',
|
|
12
|
+
green: '\x1b[32m',
|
|
13
|
+
cyan: '\x1b[36m',
|
|
14
|
+
yellow: '\x1b[33m',
|
|
15
|
+
red: '\x1b[31m',
|
|
16
|
+
magenta: '\x1b[35m',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
|
|
20
|
+
|
|
21
|
+
// ─── 프로젝트 타입 정의 ───
|
|
22
|
+
const PROJECT_TYPES = {
|
|
23
|
+
'spring-boot': {
|
|
24
|
+
label: 'Spring Boot (Kotlin/Java)',
|
|
25
|
+
buildCmd: './gradlew build -x test --no-daemon',
|
|
26
|
+
testCmd: './gradlew test --no-daemon',
|
|
27
|
+
lintCmd: '',
|
|
28
|
+
},
|
|
29
|
+
'react-native': {
|
|
30
|
+
label: 'React Native (TypeScript)',
|
|
31
|
+
buildCmd: '',
|
|
32
|
+
testCmd: '',
|
|
33
|
+
lintCmd: 'npx tsc --noEmit',
|
|
34
|
+
},
|
|
35
|
+
nextjs: {
|
|
36
|
+
label: 'Next.js (TypeScript)',
|
|
37
|
+
buildCmd: 'npm run build',
|
|
38
|
+
testCmd: 'npm test',
|
|
39
|
+
lintCmd: 'npx next lint',
|
|
40
|
+
},
|
|
41
|
+
custom: {
|
|
42
|
+
label: 'Custom (직접 설정)',
|
|
43
|
+
buildCmd: '',
|
|
44
|
+
testCmd: '',
|
|
45
|
+
lintCmd: '',
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// ─── CLI 인자 파싱 ───
|
|
50
|
+
function parseArgs() {
|
|
51
|
+
const args = process.argv.slice(2);
|
|
52
|
+
const parsed = {};
|
|
53
|
+
for (let i = 0; i < args.length; i++) {
|
|
54
|
+
if (args[i] === '--type' && args[i + 1]) parsed.type = args[++i];
|
|
55
|
+
else if (args[i] === '--name' && args[i + 1]) parsed.name = args[++i];
|
|
56
|
+
else if (args[i] === '--role' && args[i + 1]) parsed.role = args[++i];
|
|
57
|
+
else if (args[i] === '--figma-key' && args[i + 1]) parsed.figmaKey = args[++i];
|
|
58
|
+
else if (args[i] === '--interval' && args[i + 1]) parsed.interval = args[++i];
|
|
59
|
+
else if (args[i] === '--force' || args[i] === '-f') parsed.force = true;
|
|
60
|
+
else if (args[i] === '--help' || args[i] === '-h') {
|
|
61
|
+
console.log(`
|
|
62
|
+
사용법: sleepcode [옵션]
|
|
63
|
+
|
|
64
|
+
옵션 없이 실행하면 인터랙티브 모드로 동작합니다.
|
|
65
|
+
|
|
66
|
+
옵션:
|
|
67
|
+
--type <type> 프로젝트 타입 (spring-boot, react-native, nextjs, custom)
|
|
68
|
+
--name <name> 프로젝트 이름
|
|
69
|
+
--role <desc> AI 역할 설명
|
|
70
|
+
--figma-key <key> Figma API Key
|
|
71
|
+
--interval <sec> 반복 간격 (초, 기본 30)
|
|
72
|
+
-f, --force 기존 .ai/ 덮어쓰기
|
|
73
|
+
-h, --help 도움말
|
|
74
|
+
`);
|
|
75
|
+
process.exit(0);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return parsed;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ─── 유틸 ───
|
|
82
|
+
function ask(rl, question, defaultVal) {
|
|
83
|
+
const suffix = defaultVal ? ` ${C.dim}(${defaultVal})${C.reset}` : '';
|
|
84
|
+
return new Promise((resolve) => {
|
|
85
|
+
rl.question(`${C.cyan}?${C.reset} ${question}${suffix}: `, (answer) => {
|
|
86
|
+
resolve(answer.trim() || defaultVal || '');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function select(rl, question, options) {
|
|
92
|
+
return new Promise((resolve) => {
|
|
93
|
+
console.log(`\n${C.cyan}?${C.reset} ${question}`);
|
|
94
|
+
options.forEach((opt, i) => {
|
|
95
|
+
console.log(` ${C.bold}${i + 1})${C.reset} ${opt.label}`);
|
|
96
|
+
});
|
|
97
|
+
rl.question(`${C.cyan}>${C.reset} 번호 선택: `, (answer) => {
|
|
98
|
+
const idx = parseInt(answer, 10) - 1;
|
|
99
|
+
if (idx >= 0 && idx < options.length) {
|
|
100
|
+
resolve(options[idx]);
|
|
101
|
+
} else {
|
|
102
|
+
resolve(options[0]);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function writeFile(filePath, content) {
|
|
109
|
+
const dir = path.dirname(filePath);
|
|
110
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
111
|
+
fs.writeFileSync(filePath, content);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function generateFiles(targetDir, { typeKey, projectName, role, buildCmd, testCmd, lintCmd, figmaKey, sleepInterval }) {
|
|
115
|
+
const aiDir = path.join(targetDir, '.ai');
|
|
116
|
+
const claudeDir = path.join(targetDir, '.claude');
|
|
117
|
+
fs.mkdirSync(path.join(aiDir, 'docs'), { recursive: true });
|
|
118
|
+
fs.mkdirSync(path.join(aiDir, 'logs'), { recursive: true });
|
|
119
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
120
|
+
|
|
121
|
+
// 공통 파일 복사
|
|
122
|
+
const commonFiles = ['ai_worker.sh', 'run_forever.sh', 'log_filter.py', 'README.md'];
|
|
123
|
+
for (const file of commonFiles) {
|
|
124
|
+
const src = path.join(TEMPLATES_DIR, 'common', file);
|
|
125
|
+
const dest = path.join(aiDir, file);
|
|
126
|
+
if (fs.existsSync(src)) {
|
|
127
|
+
let content = fs.readFileSync(src, 'utf-8');
|
|
128
|
+
content = content.replace(/\{\{SLEEP_INTERVAL\}\}/g, sleepInterval);
|
|
129
|
+
fs.writeFileSync(dest, content);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 실행 권한
|
|
134
|
+
fs.chmodSync(path.join(aiDir, 'ai_worker.sh'), 0o755);
|
|
135
|
+
fs.chmodSync(path.join(aiDir, 'run_forever.sh'), 0o755);
|
|
136
|
+
fs.chmodSync(path.join(aiDir, 'log_filter.py'), 0o755);
|
|
137
|
+
|
|
138
|
+
// docs/.gitkeep
|
|
139
|
+
writeFile(path.join(aiDir, 'docs', '.gitkeep'), '');
|
|
140
|
+
|
|
141
|
+
// tasks.md
|
|
142
|
+
writeFile(
|
|
143
|
+
path.join(aiDir, 'tasks.md'),
|
|
144
|
+
`# 작업 목록
|
|
145
|
+
|
|
146
|
+
아래 태스크를 순서대로 진행하세요. 완료한 항목은 \`[x]\`로 체크하세요.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
- [ ] 여기에 첫 번째 작업을 적어주세요
|
|
151
|
+
`
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
// rules.md
|
|
155
|
+
const rulesTemplate = path.join(TEMPLATES_DIR, 'rules', `${typeKey}.md`);
|
|
156
|
+
if (fs.existsSync(rulesTemplate)) {
|
|
157
|
+
let rules = fs.readFileSync(rulesTemplate, 'utf-8');
|
|
158
|
+
rules = rules.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
|
|
159
|
+
rules = rules.replace(/\{\{ROLE\}\}/g, role);
|
|
160
|
+
rules = rules.replace(/\{\{BUILD_CMD\}\}/g, buildCmd);
|
|
161
|
+
rules = rules.replace(/\{\{TEST_CMD\}\}/g, testCmd);
|
|
162
|
+
rules = rules.replace(/\{\{LINT_CMD\}\}/g, lintCmd);
|
|
163
|
+
rules = rules.replace(/\{\{FIGMA_API_KEY\}\}/g, figmaKey);
|
|
164
|
+
|
|
165
|
+
if (!figmaKey) {
|
|
166
|
+
rules = rules.replace(/\n## Figma[\s\S]*?(?=\n## |$)/, '');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
writeFile(path.join(aiDir, 'rules.md'), rules);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// settings.local.json
|
|
173
|
+
const settingsTemplate = path.join(TEMPLATES_DIR, 'settings', `${typeKey}.json`);
|
|
174
|
+
if (fs.existsSync(settingsTemplate)) {
|
|
175
|
+
const content = fs.readFileSync(settingsTemplate, 'utf-8');
|
|
176
|
+
fs.writeFileSync(path.join(claudeDir, 'settings.local.json'), content);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// .gitignore
|
|
180
|
+
const gitignorePath = path.join(targetDir, '.gitignore');
|
|
181
|
+
if (fs.existsSync(gitignorePath)) {
|
|
182
|
+
const gitignore = fs.readFileSync(gitignorePath, 'utf-8');
|
|
183
|
+
if (!gitignore.includes('.ai/logs/')) {
|
|
184
|
+
fs.appendFileSync(gitignorePath, '\n# AI worker logs\n.ai/logs/\n');
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function printResult() {
|
|
190
|
+
console.log(`\n${C.bold}파일 생성 완료:${C.reset}\n`);
|
|
191
|
+
console.log(` ${C.green}✓${C.reset} .ai/rules.md`);
|
|
192
|
+
console.log(` ${C.green}✓${C.reset} .ai/tasks.md`);
|
|
193
|
+
console.log(` ${C.green}✓${C.reset} .ai/ai_worker.sh`);
|
|
194
|
+
console.log(` ${C.green}✓${C.reset} .ai/run_forever.sh`);
|
|
195
|
+
console.log(` ${C.green}✓${C.reset} .ai/log_filter.py`);
|
|
196
|
+
console.log(` ${C.green}✓${C.reset} .ai/README.md`);
|
|
197
|
+
console.log(` ${C.green}✓${C.reset} .ai/docs/`);
|
|
198
|
+
console.log(` ${C.green}✓${C.reset} .claude/settings.local.json`);
|
|
199
|
+
|
|
200
|
+
console.log(`
|
|
201
|
+
${C.bold}${C.green}완료!${C.reset} 다음 단계:
|
|
202
|
+
|
|
203
|
+
${C.bold}1.${C.reset} .ai/rules.md 를 프로젝트에 맞게 수정
|
|
204
|
+
${C.bold}2.${C.reset} .ai/tasks.md 에 작업 목록 작성
|
|
205
|
+
${C.bold}3.${C.reset} 실행:
|
|
206
|
+
${C.dim}# 1회 실행${C.reset}
|
|
207
|
+
./.ai/ai_worker.sh
|
|
208
|
+
|
|
209
|
+
${C.dim}# 무한 루프 (tmux)${C.reset}
|
|
210
|
+
tmux new -s ai './.ai/run_forever.sh'
|
|
211
|
+
`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ─── 메인 ───
|
|
215
|
+
async function main() {
|
|
216
|
+
const targetDir = process.cwd();
|
|
217
|
+
const cliArgs = parseArgs();
|
|
218
|
+
|
|
219
|
+
console.log(`
|
|
220
|
+
${C.bold}${C.magenta} ╔══════════════════════════════════╗
|
|
221
|
+
║ sleepcode ║
|
|
222
|
+
║ AI codes while you sleep ║
|
|
223
|
+
╚══════════════════════════════════╝${C.reset}
|
|
224
|
+
`);
|
|
225
|
+
|
|
226
|
+
// 비대화형 모드: --type 이 있으면 인터랙티브 스킵
|
|
227
|
+
if (cliArgs.type) {
|
|
228
|
+
const typeKey = cliArgs.type;
|
|
229
|
+
if (!PROJECT_TYPES[typeKey]) {
|
|
230
|
+
console.error(`${C.red}알 수 없는 타입: ${typeKey}${C.reset}`);
|
|
231
|
+
console.error(`사용 가능: ${Object.keys(PROJECT_TYPES).join(', ')}`);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (fs.existsSync(path.join(targetDir, '.ai')) && !cliArgs.force) {
|
|
236
|
+
console.error(`${C.red}.ai/ 폴더가 이미 존재합니다. --force 로 덮어쓰세요.${C.reset}`);
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const typeConfig = PROJECT_TYPES[typeKey];
|
|
241
|
+
const projectName = cliArgs.name || path.basename(targetDir);
|
|
242
|
+
const role = cliArgs.role || `${projectName} 서비스 개발`;
|
|
243
|
+
const figmaKey = cliArgs.figmaKey || '';
|
|
244
|
+
const sleepInterval = cliArgs.interval || '30';
|
|
245
|
+
|
|
246
|
+
console.log(`${C.dim}타입: ${typeConfig.label}${C.reset}`);
|
|
247
|
+
console.log(`${C.dim}이름: ${projectName}${C.reset}`);
|
|
248
|
+
console.log(`${C.dim}역할: ${role}${C.reset}`);
|
|
249
|
+
|
|
250
|
+
generateFiles(targetDir, {
|
|
251
|
+
typeKey,
|
|
252
|
+
projectName,
|
|
253
|
+
role,
|
|
254
|
+
buildCmd: typeConfig.buildCmd,
|
|
255
|
+
testCmd: typeConfig.testCmd,
|
|
256
|
+
lintCmd: typeConfig.lintCmd,
|
|
257
|
+
figmaKey,
|
|
258
|
+
sleepInterval,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
printResult();
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// 인터랙티브 모드
|
|
266
|
+
const rl = readline.createInterface({
|
|
267
|
+
input: process.stdin,
|
|
268
|
+
output: process.stdout,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
if (fs.existsSync(path.join(targetDir, '.ai'))) {
|
|
273
|
+
console.log(`${C.yellow}⚠ .ai/ 폴더가 이미 존재합니다.${C.reset}`);
|
|
274
|
+
const overwrite = await ask(rl, '덮어쓸까요? (y/N)', 'N');
|
|
275
|
+
if (overwrite.toLowerCase() !== 'y') {
|
|
276
|
+
console.log('취소됨.');
|
|
277
|
+
rl.close();
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const typeOptions = Object.entries(PROJECT_TYPES).map(([key, val]) => ({
|
|
283
|
+
key,
|
|
284
|
+
label: val.label,
|
|
285
|
+
}));
|
|
286
|
+
const selectedType = await select(rl, '프로젝트 타입', typeOptions);
|
|
287
|
+
const typeKey = selectedType.key;
|
|
288
|
+
const typeConfig = PROJECT_TYPES[typeKey];
|
|
289
|
+
|
|
290
|
+
const projectName = await ask(rl, '프로젝트 이름', path.basename(targetDir));
|
|
291
|
+
const role = await ask(rl, 'AI 역할 설명', `${projectName} 서비스 개발`);
|
|
292
|
+
|
|
293
|
+
let buildCmd = typeConfig.buildCmd;
|
|
294
|
+
let testCmd = typeConfig.testCmd;
|
|
295
|
+
let lintCmd = typeConfig.lintCmd;
|
|
296
|
+
|
|
297
|
+
if (typeKey === 'custom') {
|
|
298
|
+
buildCmd = await ask(rl, '빌드 커맨드 (없으면 Enter)', '');
|
|
299
|
+
testCmd = await ask(rl, '테스트 커맨드 (없으면 Enter)', '');
|
|
300
|
+
lintCmd = await ask(rl, '린트 커맨드 (없으면 Enter)', '');
|
|
301
|
+
} else {
|
|
302
|
+
console.log(`${C.dim} 빌드: ${buildCmd || '(없음)'}${C.reset}`);
|
|
303
|
+
console.log(`${C.dim} 테스트: ${testCmd || '(없음)'}${C.reset}`);
|
|
304
|
+
console.log(`${C.dim} 린트: ${lintCmd || '(없음)'}${C.reset}`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const figmaKey = await ask(rl, 'Figma API Key (없으면 Enter)', '');
|
|
308
|
+
const sleepInterval = await ask(rl, '반복 간격 (초)', '30');
|
|
309
|
+
|
|
310
|
+
rl.close();
|
|
311
|
+
|
|
312
|
+
generateFiles(targetDir, {
|
|
313
|
+
typeKey,
|
|
314
|
+
projectName,
|
|
315
|
+
role,
|
|
316
|
+
buildCmd,
|
|
317
|
+
testCmd,
|
|
318
|
+
lintCmd,
|
|
319
|
+
figmaKey,
|
|
320
|
+
sleepInterval,
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
printResult();
|
|
324
|
+
} catch (e) {
|
|
325
|
+
console.error(`${C.red}오류: ${e.message}${C.reset}`);
|
|
326
|
+
rl.close();
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sleepcode",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI codes while you sleep — Claude AI 야간 자동화 세팅 CLI",
|
|
5
|
+
"bin": {
|
|
6
|
+
"sleepcode": "bin/index.js"
|
|
7
|
+
},
|
|
8
|
+
"keywords": [
|
|
9
|
+
"claude",
|
|
10
|
+
"ai",
|
|
11
|
+
"automation",
|
|
12
|
+
"coding",
|
|
13
|
+
"nightshift",
|
|
14
|
+
"sleepcode"
|
|
15
|
+
],
|
|
16
|
+
"license": "MIT"
|
|
17
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# SleepCode
|
|
2
|
+
|
|
3
|
+
AI codes while you sleep — 밤새 개발 작업을 자동화하는 시스템입니다.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 폴더 구조
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
.ai/
|
|
11
|
+
rules.md # AI 역할 + 작업 규칙
|
|
12
|
+
tasks.md # 오늘 진행할 작업 목록
|
|
13
|
+
docs/ # 개발 참고 자료 (피그마 스크린샷, 기획서 등)
|
|
14
|
+
ai_worker.sh # 1회 실행 스크립트
|
|
15
|
+
run_forever.sh # 무한 루프 감시자 스크립트
|
|
16
|
+
log_filter.py # 로그 필터 (핵심 메시지만 추출)
|
|
17
|
+
logs/ # 실행 로그 (자동 생성)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 작동 원리
|
|
23
|
+
|
|
24
|
+
1. `claude -p` 로 비대화형 모드 실행
|
|
25
|
+
2. `rules.md` + `tasks.md` 를 합쳐서 프롬프트로 전달
|
|
26
|
+
3. AI가 코드 작성 → 빌드/테스트 → 오류 수정 → git commit
|
|
27
|
+
4. 대기 후 다시 반복
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 실행 방법
|
|
32
|
+
|
|
33
|
+
### 1. 권한 부여
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
chmod +x .ai/*.sh
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 2. (최초 1회) --dangerously-skip-permissions 수락
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
claude --dangerously-skip-permissions
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
동의 프롬프트가 뜨면 수락 후 `Ctrl + C`로 나옵니다.
|
|
46
|
+
|
|
47
|
+
### 3. tmux 세션 생성 + 실행
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
tmux new -s ai './.ai/run_forever.sh'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 4. tmux 분리 (백그라운드 전환)
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
Ctrl + B → D
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 수동 1회 실행
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
./.ai/ai_worker.sh
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 관리 명령어
|
|
70
|
+
|
|
71
|
+
| 동작 | 명령어 |
|
|
72
|
+
|------|--------|
|
|
73
|
+
| 세션 재접속 | `tmux attach -t ai` |
|
|
74
|
+
| 실시간 로그 | `tail -f .ai/logs/worker_*.log` |
|
|
75
|
+
| 종료 | `tmux attach -t ai` → `Ctrl + C` |
|
|
76
|
+
| 세션 삭제 | `tmux kill-session -t ai` |
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 아침 확인
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# 밤 동안의 커밋 확인
|
|
84
|
+
git log --oneline --since="12 hours ago"
|
|
85
|
+
|
|
86
|
+
# 로그 확인
|
|
87
|
+
tail -100 .ai/logs/worker_*.log
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 커스터마이징
|
|
93
|
+
|
|
94
|
+
- **역할/규칙 변경**: `.ai/rules.md` 수정
|
|
95
|
+
- **태스크 변경**: `.ai/tasks.md` 수정
|
|
96
|
+
- **참고 자료 추가**: `.ai/docs/` 에 파일 추가
|
|
97
|
+
- **반복 간격 변경**: `run_forever.sh` 의 `sleep` 값 수정
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# AI Worker - 1회 실행 스크립트
|
|
4
|
+
# run_forever.sh (무한 루프) 대신 수동으로 1회만 돌릴 때 사용
|
|
5
|
+
|
|
6
|
+
cd "$(dirname "$0")/.." || exit 1
|
|
7
|
+
|
|
8
|
+
echo "[$(date '+%Y-%m-%d %H:%M:%S')] AI 단일 실행 시작"
|
|
9
|
+
|
|
10
|
+
RULES=$(cat .ai/rules.md)
|
|
11
|
+
TASKS=$(cat .ai/tasks.md)
|
|
12
|
+
|
|
13
|
+
PROMPT="${RULES}
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
${TASKS}"
|
|
18
|
+
|
|
19
|
+
# stream-json + verbose: 토큰 단위 실시간 출력
|
|
20
|
+
claude -p "$PROMPT" --dangerously-skip-permissions --output-format stream-json --verbose 2>&1 \
|
|
21
|
+
| python3 .ai/log_filter.py
|
|
22
|
+
|
|
23
|
+
echo "[$(date '+%Y-%m-%d %H:%M:%S')] AI 단일 실행 종료"
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
stream-json 출력에서 핵심 메시지만 추출하는 필터.
|
|
4
|
+
Usage: claude ... --output-format stream-json | python3 .ai/log_filter.py
|
|
5
|
+
"""
|
|
6
|
+
import sys
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
for line in sys.stdin:
|
|
10
|
+
line = line.strip()
|
|
11
|
+
if not line:
|
|
12
|
+
continue
|
|
13
|
+
try:
|
|
14
|
+
obj = json.loads(line)
|
|
15
|
+
except json.JSONDecodeError:
|
|
16
|
+
continue
|
|
17
|
+
|
|
18
|
+
msg_type = obj.get("type")
|
|
19
|
+
|
|
20
|
+
# assistant 메시지만 처리
|
|
21
|
+
if msg_type == "assistant":
|
|
22
|
+
message = obj.get("message", {})
|
|
23
|
+
contents = message.get("content", [])
|
|
24
|
+
for c in contents:
|
|
25
|
+
ctype = c.get("type")
|
|
26
|
+
if ctype == "text":
|
|
27
|
+
text = c.get("text", "").strip()
|
|
28
|
+
if text:
|
|
29
|
+
print(f"[TEXT] {text}", flush=True)
|
|
30
|
+
elif ctype == "tool_use":
|
|
31
|
+
name = c.get("name", "?")
|
|
32
|
+
inp = c.get("input", {})
|
|
33
|
+
# 도구별 핵심 파라미터만 요약
|
|
34
|
+
if name in ("Read", "Write", "Edit"):
|
|
35
|
+
param = inp.get("file_path", "")
|
|
36
|
+
print(f"[TOOL] {name}: {param}", flush=True)
|
|
37
|
+
elif name == "Bash":
|
|
38
|
+
cmd = inp.get("command", "")
|
|
39
|
+
if len(cmd) > 120:
|
|
40
|
+
cmd = cmd[:120] + "..."
|
|
41
|
+
print(f"[TOOL] Bash: {cmd}", flush=True)
|
|
42
|
+
elif name == "Glob":
|
|
43
|
+
print(f"[TOOL] Glob: {inp.get('pattern', '')}", flush=True)
|
|
44
|
+
elif name == "Grep":
|
|
45
|
+
print(f"[TOOL] Grep: {inp.get('pattern', '')}", flush=True)
|
|
46
|
+
elif name == "TodoWrite":
|
|
47
|
+
todos = inp.get("todos", [])
|
|
48
|
+
active = [t for t in todos if t.get("status") == "in_progress"]
|
|
49
|
+
if active:
|
|
50
|
+
print(f"[TODO] {active[0].get('activeForm', '')}", flush=True)
|
|
51
|
+
else:
|
|
52
|
+
print(f"[TOOL] {name}", flush=True)
|
|
53
|
+
|
|
54
|
+
# 최종 결과
|
|
55
|
+
elif msg_type == "result":
|
|
56
|
+
message = obj.get("message", "")
|
|
57
|
+
if isinstance(message, str) and message:
|
|
58
|
+
short = message[:200] + "..." if len(message) > 200 else message
|
|
59
|
+
print(f"[DONE] {short}", flush=True)
|
|
60
|
+
cost = obj.get("cost_usd")
|
|
61
|
+
duration = obj.get("duration_ms")
|
|
62
|
+
if cost is not None:
|
|
63
|
+
print(f"[COST] ${cost:.4f} | {(duration or 0) / 1000:.0f}s", flush=True)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# AI Night Worker - 감시자 스크립트
|
|
4
|
+
# 사용법: tmux new -s ai './.ai/run_forever.sh'
|
|
5
|
+
|
|
6
|
+
cd "$(dirname "$0")/.." || exit 1
|
|
7
|
+
|
|
8
|
+
LOG_DIR=".ai/logs"
|
|
9
|
+
mkdir -p "$LOG_DIR"
|
|
10
|
+
LOG_FILE="$LOG_DIR/worker_$(date +%Y%m%d_%H%M%S).log"
|
|
11
|
+
|
|
12
|
+
log() {
|
|
13
|
+
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
log "=== AI Night Worker 시작 ==="
|
|
17
|
+
log "로그 파일: $LOG_FILE"
|
|
18
|
+
|
|
19
|
+
ITERATION=0
|
|
20
|
+
|
|
21
|
+
while true; do
|
|
22
|
+
ITERATION=$((ITERATION + 1))
|
|
23
|
+
log "--- 반복 #${ITERATION} 시작 ---"
|
|
24
|
+
|
|
25
|
+
# 미완료 태스크가 있는지 확인
|
|
26
|
+
REMAINING=$(grep -c '\[ \]' .ai/tasks.md 2>/dev/null || echo "0")
|
|
27
|
+
log "남은 태스크: ${REMAINING}개"
|
|
28
|
+
|
|
29
|
+
if [ "$REMAINING" -eq 0 ]; then
|
|
30
|
+
log "=== 모든 태스크 완료. 종료합니다. ==="
|
|
31
|
+
exit 0
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# rules.md + tasks.md 를 합쳐서 프롬프트 구성
|
|
35
|
+
RULES=$(cat .ai/rules.md)
|
|
36
|
+
TASKS=$(cat .ai/tasks.md)
|
|
37
|
+
|
|
38
|
+
PROMPT="${RULES}
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
${TASKS}"
|
|
43
|
+
|
|
44
|
+
log "claude 실행 중..."
|
|
45
|
+
# stream-json → log_filter.py 로 핵심 메시지만 추출
|
|
46
|
+
claude -p "$PROMPT" --dangerously-skip-permissions --output-format stream-json --verbose 2>&1 \
|
|
47
|
+
| python3 .ai/log_filter.py \
|
|
48
|
+
| tee -a "$LOG_FILE"
|
|
49
|
+
EXIT_CODE=${PIPESTATUS[0]}
|
|
50
|
+
log "claude 종료 (exit code: $EXIT_CODE)"
|
|
51
|
+
|
|
52
|
+
# 미커밋 변경사항 체크
|
|
53
|
+
if [[ -n $(git status --porcelain) ]]; then
|
|
54
|
+
log "경고: 커밋되지 않은 변경사항 감지"
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
log "--- 반복 #${ITERATION} 종료, {{SLEEP_INTERVAL}}초 대기 ---"
|
|
58
|
+
sleep {{SLEEP_INTERVAL}}
|
|
59
|
+
done
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# AI 작업 설정
|
|
2
|
+
|
|
3
|
+
## 역할
|
|
4
|
+
|
|
5
|
+
당신은 시니어 개발자입니다.
|
|
6
|
+
|
|
7
|
+
목표: {{ROLE}}
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 기본 원칙
|
|
12
|
+
|
|
13
|
+
- 절대로 사용자에게 질문하지 않는다.
|
|
14
|
+
- 작업이 끝났다고 판단하지 않는다.
|
|
15
|
+
- 항상 다음 작업을 스스로 찾는다.
|
|
16
|
+
- 가능한 한 많은 기능을 구현하며 계속 진행한다.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 참고 자료
|
|
21
|
+
|
|
22
|
+
- `.ai/docs/` 디렉토리에 업로드된 파일 참고
|
|
23
|
+
|
|
24
|
+
## Figma
|
|
25
|
+
|
|
26
|
+
- **프론트엔드 디자인**: Figma MCP 도구로 직접 조회 가능 (API Key: `{{FIGMA_API_KEY}}`)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 작업 방식
|
|
31
|
+
|
|
32
|
+
- 작은 단위로 작업하고 자주 확인한다.
|
|
33
|
+
- 오류가 발생하면 자동으로 수정한다.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 문제 해결
|
|
38
|
+
|
|
39
|
+
- 오류가 발생하면 원인을 분석하고 직접 수정한다.
|
|
40
|
+
- 누락된 파일이 있으면 생성한다.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 코드 작성 규칙
|
|
45
|
+
|
|
46
|
+
- 기존 프로젝트 구조와 패턴을 존중한다.
|
|
47
|
+
- 중복 코드를 만들지 않는다.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 태스크 완료 처리
|
|
52
|
+
|
|
53
|
+
- `.ai/tasks.md` 파일에서 작업 목록을 확인한다.
|
|
54
|
+
- 태스크를 **한 항목씩** 순서대로 진행한다.
|
|
55
|
+
- 한 항목을 완료하면 반드시 아래 순서를 따른다:
|
|
56
|
+
1. 해당 항목의 `[ ]`를 `[x]`로 변경한다.
|
|
57
|
+
2. 관련 파일을 모두 `git add` 한다. (tasks.md 포함)
|
|
58
|
+
3. `git commit` 한다. (커밋 메시지에 태스크 내용을 포함)
|
|
59
|
+
4. 그 다음 항목으로 넘어간다.
|
|
60
|
+
- 여러 항목을 한꺼번에 작업하지 않는다. 반드시 1항목 = 1커밋이다.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Git 작업 규칙
|
|
65
|
+
|
|
66
|
+
- tasks.md의 항목 1개 완료 = git commit 1개. 이 규칙을 반드시 지킨다.
|
|
67
|
+
- 작업 중간에는 commit 하지 않는다.
|
|
68
|
+
- 기능이 정상 동작한다고 판단되면 commit 한다.
|
|
69
|
+
- commit message 는 변경 내용을 구체적으로 설명한다.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# AI 작업 설정
|
|
2
|
+
|
|
3
|
+
## 역할
|
|
4
|
+
|
|
5
|
+
당신은 Next.js + TypeScript 시니어 풀스택 개발자입니다.
|
|
6
|
+
|
|
7
|
+
목표: {{ROLE}}
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 기본 원칙
|
|
12
|
+
|
|
13
|
+
- 절대로 사용자에게 질문하지 않는다.
|
|
14
|
+
- 작업이 끝났다고 판단하지 않는다.
|
|
15
|
+
- 항상 다음 작업을 스스로 찾는다.
|
|
16
|
+
- 가능한 한 많은 기능을 구현하며 계속 진행한다.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 참고 자료
|
|
21
|
+
|
|
22
|
+
- `.ai/docs/` 디렉토리에 업로드된 파일 참고 (피그마 스크린샷, 기획서, 참고 이미지 등)
|
|
23
|
+
|
|
24
|
+
## Figma
|
|
25
|
+
|
|
26
|
+
- **프론트엔드 디자인**: Figma MCP 도구로 직접 조회 가능 (API Key: `{{FIGMA_API_KEY}}`)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 작업 방식
|
|
31
|
+
|
|
32
|
+
- 작은 단위로 작업하고 자주 확인한다.
|
|
33
|
+
- 코드 작성 후 반드시 빌드를 시도한다: `{{BUILD_CMD}}`
|
|
34
|
+
- 린트도 확인한다: `{{LINT_CMD}}`
|
|
35
|
+
- 오류가 발생하면 자동으로 수정한다.
|
|
36
|
+
- 수정 후 반드시 다시 빌드를 시도한다.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 문제 해결
|
|
41
|
+
|
|
42
|
+
- 타입 오류가 발생하면 원인을 분석하고 직접 수정한다.
|
|
43
|
+
- 누락된 파일이나 컴포넌트가 있으면 생성한다.
|
|
44
|
+
- import 경로가 잘못되었으면 수정한다.
|
|
45
|
+
- 필요한 패키지가 없으면 `npm install`로 설치한다.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 코드 작성 규칙
|
|
50
|
+
|
|
51
|
+
- TypeScript strict 모드를 준수한다.
|
|
52
|
+
- 기존 프로젝트 구조와 패턴을 존중한다.
|
|
53
|
+
- 중복 코드를 만들지 않는다.
|
|
54
|
+
- App Router 구조를 따른다.
|
|
55
|
+
- Server Component / Client Component 를 적절히 구분한다.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 태스크 완료 처리
|
|
60
|
+
|
|
61
|
+
- `.ai/tasks.md` 파일에서 작업 목록을 확인한다.
|
|
62
|
+
- 태스크를 **한 항목씩** 순서대로 진행한다.
|
|
63
|
+
- 한 항목을 완료하면 반드시 아래 순서를 따른다:
|
|
64
|
+
1. 해당 항목의 `[ ]`를 `[x]`로 변경한다.
|
|
65
|
+
2. 관련 파일을 모두 `git add` 한다. (tasks.md 포함)
|
|
66
|
+
3. `git commit` 한다. (커밋 메시지에 태스크 내용을 포함)
|
|
67
|
+
4. 그 다음 항목으로 넘어간다.
|
|
68
|
+
- 여러 항목을 한꺼번에 작업하지 않는다. 반드시 1항목 = 1커밋이다.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Git 작업 규칙
|
|
73
|
+
|
|
74
|
+
- tasks.md의 항목 1개 완료 = git commit 1개. 이 규칙을 반드시 지킨다.
|
|
75
|
+
- 작업 중간에는 commit 하지 않는다.
|
|
76
|
+
- 기능이 정상 동작한다고 판단되면 commit 한다.
|
|
77
|
+
- commit message 는 변경 내용을 구체적으로 설명한다.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# AI 작업 설정
|
|
2
|
+
|
|
3
|
+
## 역할
|
|
4
|
+
|
|
5
|
+
당신은 React Native + TypeScript 시니어 모바일 개발자입니다.
|
|
6
|
+
|
|
7
|
+
목표: {{ROLE}}
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 기본 원칙
|
|
12
|
+
|
|
13
|
+
- 절대로 사용자에게 질문하지 않는다.
|
|
14
|
+
- 작업이 끝났다고 판단하지 않는다.
|
|
15
|
+
- 항상 다음 작업을 스스로 찾는다.
|
|
16
|
+
- 가능한 한 많은 기능을 구현하며 계속 진행한다.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 참고 자료
|
|
21
|
+
|
|
22
|
+
- `.ai/docs/` 디렉토리에 업로드된 파일 참고 (피그마 스크린샷, 기획서, 참고 이미지 등)
|
|
23
|
+
|
|
24
|
+
## Figma
|
|
25
|
+
|
|
26
|
+
- **프론트엔드 디자인**: Figma MCP 도구로 직접 조회 가능 (API Key: `{{FIGMA_API_KEY}}`)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 작업 방식
|
|
31
|
+
|
|
32
|
+
- 작은 단위로 작업하고 자주 확인한다.
|
|
33
|
+
- 코드 작성 후 반드시 타입 체크를 시도한다: `{{LINT_CMD}}`
|
|
34
|
+
- 오류가 발생하면 자동으로 수정한다.
|
|
35
|
+
- 수정 후 반드시 다시 타입 체크를 시도한다.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 문제 해결
|
|
40
|
+
|
|
41
|
+
- 타입 오류가 발생하면 원인을 분석하고 직접 수정한다.
|
|
42
|
+
- 누락된 파일이나 컴포넌트가 있으면 생성한다.
|
|
43
|
+
- import 경로가 잘못되었으면 수정한다.
|
|
44
|
+
- 필요한 패키지가 없으면 `npm install`로 설치한다.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 코드 작성 규칙
|
|
49
|
+
|
|
50
|
+
- TypeScript strict 모드를 준수한다.
|
|
51
|
+
- 기존 프로젝트 구조와 패턴을 존중한다.
|
|
52
|
+
- 중복 코드를 만들지 않는다.
|
|
53
|
+
- 기존 컴포넌트를 최대한 재사용한다.
|
|
54
|
+
- 새 컴포넌트는 기존 디렉토리 구조에 맞게 배치한다.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 태스크 완료 처리
|
|
59
|
+
|
|
60
|
+
- `.ai/tasks.md` 파일에서 작업 목록을 확인한다.
|
|
61
|
+
- 태스크를 **한 항목씩** 순서대로 진행한다.
|
|
62
|
+
- 한 항목을 완료하면 반드시 아래 순서를 따른다:
|
|
63
|
+
1. 해당 항목의 `[ ]`를 `[x]`로 변경한다.
|
|
64
|
+
2. 관련 파일을 모두 `git add` 한다. (tasks.md 포함)
|
|
65
|
+
3. `git commit` 한다. (커밋 메시지에 태스크 내용을 포함)
|
|
66
|
+
4. 그 다음 항목으로 넘어간다.
|
|
67
|
+
- 여러 항목을 한꺼번에 작업하지 않는다. 반드시 1항목 = 1커밋이다.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Git 작업 규칙
|
|
72
|
+
|
|
73
|
+
- tasks.md의 항목 1개 완료 = git commit 1개. 이 규칙을 반드시 지킨다.
|
|
74
|
+
- 작업 중간에는 commit 하지 않는다.
|
|
75
|
+
- 기능이 정상 동작한다고 판단되면 commit 한다.
|
|
76
|
+
- commit message 는 변경 내용을 구체적으로 설명한다.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# AI 작업 설정
|
|
2
|
+
|
|
3
|
+
## 역할
|
|
4
|
+
|
|
5
|
+
당신은 Spring Boot + Kotlin을 사용하는 시니어 백엔드 개발자입니다.
|
|
6
|
+
|
|
7
|
+
목표: {{ROLE}}
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 기본 원칙
|
|
12
|
+
|
|
13
|
+
- 절대로 사용자에게 질문하지 않는다.
|
|
14
|
+
- 작업이 끝났다고 판단하지 않는다.
|
|
15
|
+
- 항상 다음 작업을 스스로 찾는다.
|
|
16
|
+
- 가능한 한 많은 기능을 구현하며 계속 진행한다.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 참고 자료
|
|
21
|
+
|
|
22
|
+
- `.ai/docs/` 디렉토리에 업로드된 파일 참고 (ERD, 화면설계, API 명세 등)
|
|
23
|
+
|
|
24
|
+
## Figma
|
|
25
|
+
|
|
26
|
+
- **프론트엔드 디자인**: Figma MCP 도구로 직접 조회 가능 (API Key: `{{FIGMA_API_KEY}}`)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 작업 방식
|
|
31
|
+
|
|
32
|
+
- 작은 단위로 작업하고 자주 테스트한다.
|
|
33
|
+
- 코드 작성 후 반드시 빌드를 시도한다: `{{BUILD_CMD}}`
|
|
34
|
+
- 테스트 코드 작성 후 반드시 테스트를 실행한다: `{{TEST_CMD}}`
|
|
35
|
+
- 오류가 발생하면 자동으로 수정한다.
|
|
36
|
+
- 수정 후 반드시 다시 빌드/테스트를 시도한다.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 문제 해결
|
|
41
|
+
|
|
42
|
+
- 컴파일 오류가 발생하면 원인을 분석하고 직접 수정한다.
|
|
43
|
+
- 누락된 파일이나 클래스가 있으면 생성한다.
|
|
44
|
+
- 필요한 패키지 구조가 없으면 직접 만든다.
|
|
45
|
+
- 테스트 실패 시 테스트 코드 또는 구현 코드를 수정한다.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 코드 작성 규칙
|
|
50
|
+
|
|
51
|
+
- Kotlin 코딩 컨벤션을 따른다.
|
|
52
|
+
- 기존 프로젝트 구조와 패턴을 존중한다.
|
|
53
|
+
- 중복 코드를 만들지 않는다.
|
|
54
|
+
- REST API는 일관된 응답 형식을 유지한다.
|
|
55
|
+
- Entity ↔ DTO 변환을 명확히 분리한다.
|
|
56
|
+
- Service 계층에 비즈니스 로직을 집중한다.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 태스크 완료 처리
|
|
61
|
+
|
|
62
|
+
- `.ai/tasks.md` 파일에서 작업 목록을 확인한다.
|
|
63
|
+
- 태스크를 **한 항목씩** 순서대로 진행한다.
|
|
64
|
+
- 한 항목을 완료하면 반드시 아래 순서를 따른다:
|
|
65
|
+
1. 해당 항목의 `[ ]`를 `[x]`로 변경한다.
|
|
66
|
+
2. 관련 파일을 모두 `git add` 한다. (tasks.md 포함)
|
|
67
|
+
3. `git commit` 한다. (커밋 메시지에 태스크 내용을 포함)
|
|
68
|
+
4. 그 다음 항목으로 넘어간다.
|
|
69
|
+
- 여러 항목을 한꺼번에 작업하지 않는다. 반드시 1항목 = 1커밋이다.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Git 작업 규칙
|
|
74
|
+
|
|
75
|
+
- tasks.md의 항목 1개 완료 = git commit 1개. 이 규칙을 반드시 지킨다.
|
|
76
|
+
- 작업 중간에는 commit 하지 않는다.
|
|
77
|
+
- 기능이 정상 동작한다고 판단되면 commit 한다.
|
|
78
|
+
- commit message 는 변경 내용을 구체적으로 설명한다.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(git add:*)",
|
|
5
|
+
"Bash(git commit:*)",
|
|
6
|
+
"Bash(git status:*)",
|
|
7
|
+
"Bash(git log:*)",
|
|
8
|
+
"Bash(git diff:*)",
|
|
9
|
+
"Bash(git checkout:*)",
|
|
10
|
+
"Bash(git reset:*)",
|
|
11
|
+
"Bash(python3:*)",
|
|
12
|
+
"Bash(ls:*)",
|
|
13
|
+
"Bash(find:*)",
|
|
14
|
+
"Bash(grep:*)",
|
|
15
|
+
"Bash(cat:*)",
|
|
16
|
+
"Bash(chmod:*)"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(npm install:*)",
|
|
5
|
+
"Bash(npm uninstall:*)",
|
|
6
|
+
"Bash(npm run:*)",
|
|
7
|
+
"Bash(npm start:*)",
|
|
8
|
+
"Bash(npm ls:*)",
|
|
9
|
+
"Bash(npm test:*)",
|
|
10
|
+
"Bash(npx tsc:*)",
|
|
11
|
+
"Bash(npx eslint:*)",
|
|
12
|
+
"Bash(npx next:*)",
|
|
13
|
+
"Bash(npx prisma:*)",
|
|
14
|
+
"Bash(git add:*)",
|
|
15
|
+
"Bash(git commit:*)",
|
|
16
|
+
"Bash(git status:*)",
|
|
17
|
+
"Bash(git log:*)",
|
|
18
|
+
"Bash(git diff:*)",
|
|
19
|
+
"Bash(git checkout:*)",
|
|
20
|
+
"Bash(git reset:*)",
|
|
21
|
+
"Bash(node:*)",
|
|
22
|
+
"Bash(python3:*)",
|
|
23
|
+
"Bash(ls:*)",
|
|
24
|
+
"Bash(find:*)",
|
|
25
|
+
"Bash(grep:*)",
|
|
26
|
+
"Bash(cat:*)",
|
|
27
|
+
"Bash(chmod:*)"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(npm install:*)",
|
|
5
|
+
"Bash(npm uninstall:*)",
|
|
6
|
+
"Bash(npm run:*)",
|
|
7
|
+
"Bash(npm start:*)",
|
|
8
|
+
"Bash(npm ls:*)",
|
|
9
|
+
"Bash(npx tsc:*)",
|
|
10
|
+
"Bash(npx eslint:*)",
|
|
11
|
+
"Bash(npx react-native:*)",
|
|
12
|
+
"Bash(yarn add:*)",
|
|
13
|
+
"Bash(pod install:*)",
|
|
14
|
+
"Bash(git add:*)",
|
|
15
|
+
"Bash(git commit:*)",
|
|
16
|
+
"Bash(git status:*)",
|
|
17
|
+
"Bash(git log:*)",
|
|
18
|
+
"Bash(git diff:*)",
|
|
19
|
+
"Bash(git checkout:*)",
|
|
20
|
+
"Bash(git reset:*)",
|
|
21
|
+
"Bash(node:*)",
|
|
22
|
+
"Bash(python3:*)",
|
|
23
|
+
"Bash(ls:*)",
|
|
24
|
+
"Bash(find:*)",
|
|
25
|
+
"Bash(grep:*)",
|
|
26
|
+
"Bash(cat:*)",
|
|
27
|
+
"Bash(chmod:*)"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(./gradlew build:*)",
|
|
5
|
+
"Bash(./gradlew test:*)",
|
|
6
|
+
"Bash(./gradlew bootRun:*)",
|
|
7
|
+
"Bash(./gradlew:*)",
|
|
8
|
+
"Bash(git add:*)",
|
|
9
|
+
"Bash(git commit:*)",
|
|
10
|
+
"Bash(git status:*)",
|
|
11
|
+
"Bash(git log:*)",
|
|
12
|
+
"Bash(git diff:*)",
|
|
13
|
+
"Bash(git checkout:*)",
|
|
14
|
+
"Bash(git reset:*)",
|
|
15
|
+
"Bash(python3:*)",
|
|
16
|
+
"Bash(curl:*)",
|
|
17
|
+
"Bash(ls:*)",
|
|
18
|
+
"Bash(find:*)",
|
|
19
|
+
"Bash(grep:*)",
|
|
20
|
+
"Bash(cat:*)",
|
|
21
|
+
"Bash(chmod:*)"
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
}
|