jun-claude-code 0.0.2 → 0.0.4
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/.claude/agents/project-task-manager.md +146 -0
- package/.claude/hooks/task-loader.sh +66 -0
- package/.claude/project.env.example +7 -0
- package/README.md +47 -0
- package/dist/cli.js +51 -0
- package/dist/copy.js +10 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/init-project.d.ts +4 -0
- package/dist/init-project.js +160 -0
- package/package.json +1 -1
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: project-task-manager
|
|
3
|
+
description: GitHub Project 태스크 관리 Agent. 태스크 조회, 생성, 상태 변경, 이슈 연동.
|
|
4
|
+
keywords: [GitHub, Project, 태스크, 이슈, 할일, 백로그, 진행중, task, issue, project board]
|
|
5
|
+
model: haiku
|
|
6
|
+
color: green
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Project Task Manager Agent
|
|
10
|
+
|
|
11
|
+
GitHub Project 태스크를 관리하는 Agent입니다.
|
|
12
|
+
|
|
13
|
+
## 역할
|
|
14
|
+
|
|
15
|
+
1. **태스크 조회**: 프로젝트 보드의 태스크 목록 조회
|
|
16
|
+
2. **태스크 생성**: 새 이슈 생성 및 프로젝트에 추가
|
|
17
|
+
3. **상태 변경**: 태스크 상태 업데이트 (Backlog/In progress/Done)
|
|
18
|
+
4. **태스크 검색**: 키워드/상태별 필터링
|
|
19
|
+
|
|
20
|
+
## 프로젝트 설정
|
|
21
|
+
|
|
22
|
+
이 Agent는 `.claude/project.env` 파일에서 프로젝트 정보를 읽습니다.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# .claude/project.env
|
|
26
|
+
GITHUB_PROJECT_OWNER=your-org
|
|
27
|
+
GITHUB_PROJECT_NUMBER=1
|
|
28
|
+
GITHUB_PROJECT_REPO=your-org/your-repo
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
설정이 없으면 `jun-claude-code init-project` 명령어로 생성할 수 있습니다.
|
|
32
|
+
|
|
33
|
+
### 환경변수 로드
|
|
34
|
+
|
|
35
|
+
모든 명령어 실행 전 환경변수를 로드하세요:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
source .claude/project.env
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 사용 시점
|
|
44
|
+
|
|
45
|
+
### 적합한 경우
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
- "현재 태스크 목록 보여줘"
|
|
49
|
+
- "새 태스크 추가해줘"
|
|
50
|
+
- "이 태스크를 In progress로 변경해줘"
|
|
51
|
+
- "Backlog 태스크만 보여줘"
|
|
52
|
+
- "이슈 #123을 프로젝트에 추가해줘"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 부적합한 경우
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
- 코드 작업 (code-writer 사용)
|
|
59
|
+
- PR 생성/관리 (git-manager 사용)
|
|
60
|
+
- 코드 리뷰 (code-reviewer 사용)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 명령어 패턴
|
|
66
|
+
|
|
67
|
+
### 태스크 목록 조회
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# 전체 목록
|
|
71
|
+
gh project item-list "$GITHUB_PROJECT_NUMBER" --owner "$GITHUB_PROJECT_OWNER" --format json
|
|
72
|
+
|
|
73
|
+
# jq로 파싱
|
|
74
|
+
gh project item-list "$GITHUB_PROJECT_NUMBER" --owner "$GITHUB_PROJECT_OWNER" --format json | jq '.items[] | {title: .title, status: .status}'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 상태별 필터링
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# 특정 상태만
|
|
81
|
+
gh project item-list "$GITHUB_PROJECT_NUMBER" --owner "$GITHUB_PROJECT_OWNER" --format json | jq '[.items[] | select(.status == "In progress")]'
|
|
82
|
+
|
|
83
|
+
# 상태별 개수
|
|
84
|
+
gh project item-list "$GITHUB_PROJECT_NUMBER" --owner "$GITHUB_PROJECT_OWNER" --format json | jq '.items | group_by(.status) | map({status: .[0].status, count: length})'
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 이슈 생성 및 프로젝트 추가
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# 1. 이슈 생성
|
|
91
|
+
gh issue create --repo "$GITHUB_PROJECT_REPO" --title "제목" --body "내용"
|
|
92
|
+
|
|
93
|
+
# 2. 프로젝트에 추가
|
|
94
|
+
gh project item-add "$GITHUB_PROJECT_NUMBER" --owner "$GITHUB_PROJECT_OWNER" --url <issue-url>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 태스크 상태 변경
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# 1. 프로젝트 필드 ID 조회
|
|
101
|
+
gh project field-list "$GITHUB_PROJECT_NUMBER" --owner "$GITHUB_PROJECT_OWNER" --format json
|
|
102
|
+
|
|
103
|
+
# 2. 아이템 상태 변경
|
|
104
|
+
gh project item-edit --project-id <PROJECT_ID> --id <ITEM_ID> --field-id <FIELD_ID> --single-select-option-id <OPTION_ID>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 이슈 닫기
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
gh issue close <issue-number> --repo "$GITHUB_PROJECT_REPO"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 출력 형식
|
|
116
|
+
|
|
117
|
+
```markdown
|
|
118
|
+
# GitHub Project 태스크 현황
|
|
119
|
+
|
|
120
|
+
## 상태 요약
|
|
121
|
+
| 상태 | 개수 |
|
|
122
|
+
|------|------|
|
|
123
|
+
| In progress | N개 |
|
|
124
|
+
| Backlog | N개 |
|
|
125
|
+
| Done | N개 |
|
|
126
|
+
|
|
127
|
+
## 태스크 목록
|
|
128
|
+
|
|
129
|
+
### In progress
|
|
130
|
+
- #191 FEAT: 포트원 결제 취소 API 연동 (@0r0loo)
|
|
131
|
+
|
|
132
|
+
### Backlog
|
|
133
|
+
- #130 RFC: SKU 시스템 설계
|
|
134
|
+
- #49 S3 URL 대신 CDN URL로 응답하도록 수정
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 권한 요구사항
|
|
140
|
+
|
|
141
|
+
GitHub Project 접근에 `read:project` 및 `project` 스코프가 필요합니다.
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# 권한 부족 시
|
|
145
|
+
gh auth refresh -s read:project,project
|
|
146
|
+
```
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# 세션 시작 시 GitHub Project 태스크 조회
|
|
3
|
+
|
|
4
|
+
# project.env 로드
|
|
5
|
+
if [ -f ".claude/project.env" ]; then
|
|
6
|
+
source .claude/project.env
|
|
7
|
+
fi
|
|
8
|
+
|
|
9
|
+
# 필수 설정 확인
|
|
10
|
+
if [ -z "$GITHUB_PROJECT_OWNER" ] || [ -z "$GITHUB_PROJECT_NUMBER" ]; then
|
|
11
|
+
exit 0
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
# gh CLI 확인
|
|
15
|
+
if ! command -v gh &> /dev/null; then
|
|
16
|
+
exit 0
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
# 프로젝트 태스크 조회
|
|
20
|
+
TASKS=$(gh project item-list "$GITHUB_PROJECT_NUMBER" --owner "$GITHUB_PROJECT_OWNER" --format json 2>/dev/null)
|
|
21
|
+
|
|
22
|
+
if [ $? -ne 0 ]; then
|
|
23
|
+
echo "⚠️ GitHub Project 접근 실패. 권한 확인: gh auth refresh -s read:project,project"
|
|
24
|
+
exit 0
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# 태스크 개수 확인
|
|
28
|
+
TOTAL=$(echo "$TASKS" | jq '.totalCount // 0')
|
|
29
|
+
|
|
30
|
+
if [ "$TOTAL" -eq 0 ]; then
|
|
31
|
+
exit 0
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
{
|
|
35
|
+
echo ""
|
|
36
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
37
|
+
echo "📋 ${GITHUB_PROJECT_OWNER}/projects/${GITHUB_PROJECT_NUMBER} 태스크 현황"
|
|
38
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
39
|
+
printf "%-8s │ %-14s │ %-45s\n" "Priority" "Status" "Title"
|
|
40
|
+
echo "─────────┼────────────────┼──────────────────────────────────────────────"
|
|
41
|
+
|
|
42
|
+
# 태스크 출력 (Priority 순: P0 → P1 → P2 → 없음)
|
|
43
|
+
echo "$TASKS" | jq -r '
|
|
44
|
+
.items
|
|
45
|
+
| sort_by(
|
|
46
|
+
if .priority == "P0" then 0
|
|
47
|
+
elif .priority == "P1" then 1
|
|
48
|
+
elif .priority == "P2" then 2
|
|
49
|
+
else 3 end
|
|
50
|
+
)
|
|
51
|
+
| .[]
|
|
52
|
+
| "\(.priority // "-")\t\(.status // "-")\t\(.title)"
|
|
53
|
+
' | while IFS=$'\t' read -r priority status title; do
|
|
54
|
+
# Title 길이 제한 (45자)
|
|
55
|
+
if [ ${#title} -gt 42 ]; then
|
|
56
|
+
title="${title:0:42}..."
|
|
57
|
+
fi
|
|
58
|
+
printf "%-8s │ %-14s │ %-45s\n" "$priority" "$status" "$title"
|
|
59
|
+
done
|
|
60
|
+
|
|
61
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
62
|
+
echo "💡 /tasks 로 상세 목록 확인 | 전체: ${TOTAL}개"
|
|
63
|
+
echo ""
|
|
64
|
+
} > /dev/tty
|
|
65
|
+
|
|
66
|
+
exit 0
|
package/README.md
CHANGED
|
@@ -81,6 +81,53 @@ your-project/
|
|
|
81
81
|
└── CLAUDE.md # 프로젝트 설명
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
+
### GitHub Project 연동
|
|
85
|
+
|
|
86
|
+
`init-project` 커맨드로 GitHub Project 태스크 관리를 프로젝트에 연동할 수 있습니다.
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# 프로젝트 루트에서 실행
|
|
90
|
+
jun-claude-code init-project
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
실행하면 다음을 인터랙티브로 설정합니다:
|
|
94
|
+
|
|
95
|
+
1. **GitHub Owner** (org 또는 user)
|
|
96
|
+
2. **Project Number**
|
|
97
|
+
3. **Repository** (owner/repo 형식)
|
|
98
|
+
|
|
99
|
+
설정 완료 후 프로젝트에 생성되는 파일:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
your-project/
|
|
103
|
+
├── .claude/
|
|
104
|
+
│ ├── project.env # GitHub Project 설정 (GITHUB_PROJECT_OWNER 등)
|
|
105
|
+
│ ├── settings.json # StartSession hook (세션 시작 시 태스크 표시)
|
|
106
|
+
│ ├── hooks/
|
|
107
|
+
│ │ └── task-loader.sh # 태스크 조회 스크립트
|
|
108
|
+
│ └── agents/
|
|
109
|
+
│ └── project-task-manager.md # 태스크 관리 Agent
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### 수동 설정
|
|
113
|
+
|
|
114
|
+
`init-project` 대신 직접 `.claude/project.env`를 생성할 수도 있습니다:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# .claude/project.env
|
|
118
|
+
GITHUB_PROJECT_OWNER=your-org
|
|
119
|
+
GITHUB_PROJECT_NUMBER=1
|
|
120
|
+
GITHUB_PROJECT_REPO=your-org/your-repo
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### 필요 권한
|
|
124
|
+
|
|
125
|
+
GitHub Project 접근에 `read:project` 스코프가 필요합니다:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
gh auth refresh -s read:project,project
|
|
129
|
+
```
|
|
130
|
+
|
|
84
131
|
## 핵심 원칙
|
|
85
132
|
|
|
86
133
|
이 템플릿의 핵심 원칙:
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
3
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
37
|
const commander_1 = require("commander");
|
|
5
38
|
const copy_1 = require("./copy");
|
|
@@ -27,4 +60,22 @@ program
|
|
|
27
60
|
process.exit(1);
|
|
28
61
|
}
|
|
29
62
|
});
|
|
63
|
+
program
|
|
64
|
+
.command('init-project')
|
|
65
|
+
.description('Initialize GitHub Project integration in current directory')
|
|
66
|
+
.action(async () => {
|
|
67
|
+
try {
|
|
68
|
+
const { initProject } = await Promise.resolve().then(() => __importStar(require('./init-project')));
|
|
69
|
+
await initProject();
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (error instanceof Error) {
|
|
73
|
+
console.error('Error:', error.message);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
console.error('An unexpected error occurred');
|
|
77
|
+
}
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
30
81
|
program.parse();
|
package/dist/copy.js
CHANGED
|
@@ -125,8 +125,17 @@ async function copyClaudeFiles(options = {}) {
|
|
|
125
125
|
console.error(chalk_1.default.red('Error:'), 'Source .claude directory not found');
|
|
126
126
|
process.exit(1);
|
|
127
127
|
}
|
|
128
|
+
// Files to exclude from global copy (project-specific files)
|
|
129
|
+
const EXCLUDE_FROM_GLOBAL = [
|
|
130
|
+
'hooks/task-loader.sh',
|
|
131
|
+
'agents/project-task-manager.md',
|
|
132
|
+
'project.env.example',
|
|
133
|
+
];
|
|
128
134
|
// Get all files to copy
|
|
129
|
-
const
|
|
135
|
+
const allFiles = getAllFiles(sourceDir);
|
|
136
|
+
const files = allFiles.filter((file) => {
|
|
137
|
+
return !EXCLUDE_FROM_GLOBAL.includes(file);
|
|
138
|
+
});
|
|
130
139
|
if (files.length === 0) {
|
|
131
140
|
console.log(chalk_1.default.yellow('No files found in .claude directory'));
|
|
132
141
|
return;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.copyClaudeFiles = void 0;
|
|
3
|
+
exports.initProject = exports.copyClaudeFiles = void 0;
|
|
4
4
|
var copy_1 = require("./copy");
|
|
5
5
|
Object.defineProperty(exports, "copyClaudeFiles", { enumerable: true, get: function () { return copy_1.copyClaudeFiles; } });
|
|
6
|
+
var init_project_1 = require("./init-project");
|
|
7
|
+
Object.defineProperty(exports, "initProject", { enumerable: true, get: function () { return init_project_1.initProject; } });
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.initProject = initProject;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const readline = __importStar(require("readline"));
|
|
43
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
44
|
+
/**
|
|
45
|
+
* Prompt user for input using readline
|
|
46
|
+
*/
|
|
47
|
+
function askQuestion(question) {
|
|
48
|
+
const rl = readline.createInterface({
|
|
49
|
+
input: process.stdin,
|
|
50
|
+
output: process.stdout,
|
|
51
|
+
});
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
rl.question(question, (answer) => {
|
|
54
|
+
rl.close();
|
|
55
|
+
resolve(answer.trim());
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get the source .claude directory path (from package installation)
|
|
61
|
+
*/
|
|
62
|
+
function getSourceClaudeDir() {
|
|
63
|
+
return path.resolve(__dirname, '..', '.claude');
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get the project .claude directory path (current working directory)
|
|
67
|
+
*/
|
|
68
|
+
function getProjectClaudeDir() {
|
|
69
|
+
return path.join(process.cwd(), '.claude');
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Prompt user for GitHub Project configuration
|
|
73
|
+
*/
|
|
74
|
+
async function promptProjectConfig() {
|
|
75
|
+
console.log(chalk_1.default.cyan('\n📋 GitHub Project 설정\n'));
|
|
76
|
+
const owner = await askQuestion(chalk_1.default.white('GitHub Owner (org 또는 user): '));
|
|
77
|
+
if (!owner) {
|
|
78
|
+
console.log(chalk_1.default.red('Owner는 필수입니다.'));
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
const projectNumber = await askQuestion(chalk_1.default.white('Project Number: '));
|
|
82
|
+
if (!projectNumber || isNaN(Number(projectNumber))) {
|
|
83
|
+
console.log(chalk_1.default.red('유효한 Project Number를 입력하세요.'));
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
const repo = await askQuestion(chalk_1.default.white(`Repository (기본값: ${owner}/): `));
|
|
87
|
+
const finalRepo = repo || `${owner}/`;
|
|
88
|
+
return { owner, projectNumber, repo: finalRepo };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Create project.env file with GitHub Project configuration
|
|
92
|
+
*/
|
|
93
|
+
function createProjectEnv(destDir, config) {
|
|
94
|
+
const envPath = path.join(destDir, 'project.env');
|
|
95
|
+
const content = `# GitHub Project 설정\nGITHUB_PROJECT_OWNER=${config.owner}\nGITHUB_PROJECT_NUMBER=${config.projectNumber}\nGITHUB_PROJECT_REPO=${config.repo}\n`;
|
|
96
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
97
|
+
fs.writeFileSync(envPath, content, 'utf-8');
|
|
98
|
+
console.log(chalk_1.default.green(` ✓ ${path.relative(process.cwd(), envPath)}`));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Copy a project-specific file from package source to project directory
|
|
102
|
+
*/
|
|
103
|
+
function copyProjectFile(srcRelative, destDir) {
|
|
104
|
+
const srcPath = path.join(getSourceClaudeDir(), srcRelative);
|
|
105
|
+
const destPath = path.join(destDir, srcRelative);
|
|
106
|
+
if (!fs.existsSync(srcPath)) {
|
|
107
|
+
console.log(chalk_1.default.yellow(` ⚠ 소스 파일 없음: ${srcRelative}`));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
111
|
+
fs.copyFileSync(srcPath, destPath);
|
|
112
|
+
console.log(chalk_1.default.green(` ✓ ${path.relative(process.cwd(), destPath)}`));
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Merge StartSession hook into settings.json
|
|
116
|
+
*/
|
|
117
|
+
function mergeSettingsJson(destDir) {
|
|
118
|
+
const settingsPath = path.join(destDir, 'settings.json');
|
|
119
|
+
let settings = {};
|
|
120
|
+
if (fs.existsSync(settingsPath)) {
|
|
121
|
+
try {
|
|
122
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
console.log(chalk_1.default.yellow(' ⚠ 기존 settings.json 파싱 실패, 새로 생성합니다.'));
|
|
126
|
+
settings = {};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (!settings.hooks) {
|
|
130
|
+
settings.hooks = {};
|
|
131
|
+
}
|
|
132
|
+
settings.hooks.StartSession = [
|
|
133
|
+
{
|
|
134
|
+
type: 'command',
|
|
135
|
+
command: 'bash .claude/hooks/task-loader.sh',
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
139
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
140
|
+
console.log(chalk_1.default.green(` ✓ ${path.relative(process.cwd(), settingsPath)} (StartSession hook 추가)`));
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Initialize GitHub Project integration in current directory
|
|
144
|
+
*/
|
|
145
|
+
async function initProject() {
|
|
146
|
+
const config = await promptProjectConfig();
|
|
147
|
+
const destDir = getProjectClaudeDir();
|
|
148
|
+
console.log(chalk_1.default.cyan('\n🔧 프로젝트 설정 파일 생성\n'));
|
|
149
|
+
// 1. project.env 생성
|
|
150
|
+
createProjectEnv(destDir, config);
|
|
151
|
+
// 2. 프로젝트별 파일 복사
|
|
152
|
+
copyProjectFile('hooks/task-loader.sh', destDir);
|
|
153
|
+
copyProjectFile('agents/project-task-manager.md', destDir);
|
|
154
|
+
// 3. settings.json merge
|
|
155
|
+
mergeSettingsJson(destDir);
|
|
156
|
+
console.log(chalk_1.default.cyan('\n✅ GitHub Project 설정 완료!'));
|
|
157
|
+
console.log(chalk_1.default.gray(` Owner: ${config.owner}`));
|
|
158
|
+
console.log(chalk_1.default.gray(` Project: #${config.projectNumber}`));
|
|
159
|
+
console.log(chalk_1.default.gray(` Repo: ${config.repo}\n`));
|
|
160
|
+
}
|