@silbaram/artifact-driven-agent 0.1.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.
Files changed (68) hide show
  1. package/README.md +456 -0
  2. package/ai-dev-team/.gitkeep +0 -0
  3. package/ai-dev-team/README.md +44 -0
  4. package/ai-dev-team/artifacts/.gitkeep +0 -0
  5. package/ai-dev-team/artifacts/features/_template/api.md +19 -0
  6. package/ai-dev-team/artifacts/features/_template/qa.md +16 -0
  7. package/ai-dev-team/artifacts/features/_template/review.md +14 -0
  8. package/ai-dev-team/artifacts/features/_template/spec.md +28 -0
  9. package/ai-dev-team/artifacts/features/_template/ui.md +14 -0
  10. package/ai-dev-team/artifacts/rfc/RFC-0000-template.md +49 -0
  11. package/ai-dev-team/roles/.gitkeep +0 -0
  12. package/ai-dev-team/rules/.gitkeep +0 -0
  13. package/bin/cli.js +75 -0
  14. package/core/artifacts/architecture-options.md +85 -0
  15. package/core/artifacts/backlog.md +177 -0
  16. package/core/artifacts/current-sprint.md +125 -0
  17. package/core/artifacts/decision.md +72 -0
  18. package/core/artifacts/plan.md +187 -0
  19. package/core/artifacts/project.md +191 -0
  20. package/core/artifacts/qa-report.md +104 -0
  21. package/core/artifacts/review-report.md +103 -0
  22. package/core/roles/architect.md +236 -0
  23. package/core/roles/developer.md +203 -0
  24. package/core/roles/manager.md +300 -0
  25. package/core/roles/planner.md +231 -0
  26. package/core/roles/qa.md +262 -0
  27. package/core/roles/reviewer.md +280 -0
  28. package/core/rules/document-priority.md +196 -0
  29. package/core/rules/escalation.md +171 -0
  30. package/core/rules/iteration.md +236 -0
  31. package/core/rules/rfc.md +31 -0
  32. package/core/rules/rollback.md +218 -0
  33. package/docs/feature-structure.md +36 -0
  34. package/examples/todo-app/README.md +23 -0
  35. package/examples/todo-app/artifacts/backlog.md +23 -0
  36. package/examples/todo-app/artifacts/plan.md +23 -0
  37. package/examples/todo-app/artifacts/project.md +23 -0
  38. package/package.json +49 -0
  39. package/src/commands/interactive.js +101 -0
  40. package/src/commands/logs.js +81 -0
  41. package/src/commands/reset.js +66 -0
  42. package/src/commands/run.js +202 -0
  43. package/src/commands/sessions.js +70 -0
  44. package/src/commands/setup.js +128 -0
  45. package/src/commands/status.js +76 -0
  46. package/src/commands/validate.js +219 -0
  47. package/src/index.js +12 -0
  48. package/src/utils/files.js +134 -0
  49. package/templates/cli/artifacts/commands.md +262 -0
  50. package/templates/cli/artifacts/output-format.md +298 -0
  51. package/templates/cli/roles/cli-developer.md +239 -0
  52. package/templates/cli/rules/command-change.md +225 -0
  53. package/templates/game/artifacts/assets.md +148 -0
  54. package/templates/game/artifacts/game-systems.md +217 -0
  55. package/templates/game/artifacts/hud.md +199 -0
  56. package/templates/game/roles/game-logic.md +193 -0
  57. package/templates/game/roles/rendering.md +137 -0
  58. package/templates/game/rules/system-change.md +184 -0
  59. package/templates/library/artifacts/changelog.md +84 -0
  60. package/templates/library/artifacts/examples.md +157 -0
  61. package/templates/library/artifacts/public-api.md +197 -0
  62. package/templates/library/roles/library-developer.md +180 -0
  63. package/templates/library/rules/versioning.md +186 -0
  64. package/templates/web-dev/artifacts/api.md +212 -0
  65. package/templates/web-dev/artifacts/ui.md +104 -0
  66. package/templates/web-dev/roles/backend.md +134 -0
  67. package/templates/web-dev/roles/frontend.md +161 -0
  68. package/templates/web-dev/rules/api-change.md +198 -0
@@ -0,0 +1,128 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+ import inquirer from 'inquirer';
5
+ import {
6
+ getPackageRoot,
7
+ getWorkspaceDir,
8
+ getCurrentTemplateFile,
9
+ getAvailableTemplates,
10
+ copyDirMerge
11
+ } from '../utils/files.js';
12
+
13
+ export async function setup(template) {
14
+ const templates = getAvailableTemplates();
15
+
16
+ if (templates.length === 0) {
17
+ console.log(chalk.red('❌ 사용 가능한 템플릿이 없습니다.'));
18
+ process.exit(1);
19
+ }
20
+
21
+ // 템플릿 선택
22
+ if (!template) {
23
+ const answer = await inquirer.prompt([
24
+ {
25
+ type: 'list',
26
+ name: 'template',
27
+ message: '프로젝트 템플릿을 선택하세요:',
28
+ choices: templates.map(t => ({
29
+ name: getTemplateDescription(t),
30
+ value: t
31
+ }))
32
+ }
33
+ ]);
34
+ template = answer.template;
35
+ }
36
+
37
+ // 별칭 처리
38
+ const aliases = {
39
+ 'web': 'web-dev',
40
+ 'lib': 'library'
41
+ };
42
+ if (aliases[template]) {
43
+ template = aliases[template];
44
+ }
45
+
46
+ // 템플릿 유효성 검사
47
+ if (!templates.includes(template)) {
48
+ console.log(chalk.red(`❌ 알 수 없는 템플릿: ${template}`));
49
+ console.log(chalk.gray(`사용 가능: ${templates.join(', ')}`));
50
+ console.log(chalk.gray(`별칭: web → web-dev, lib → library`));
51
+ process.exit(1);
52
+ }
53
+
54
+ const packageRoot = getPackageRoot();
55
+ const workspace = getWorkspaceDir();
56
+ const coreDir = path.join(packageRoot, 'core');
57
+ const templateDir = path.join(packageRoot, 'templates', template);
58
+
59
+ console.log('');
60
+ console.log(chalk.cyan('━'.repeat(50)));
61
+ console.log(chalk.cyan.bold(`📦 ${template} 템플릿으로 세팅 중...`));
62
+ console.log(chalk.cyan('━'.repeat(50)));
63
+ console.log('');
64
+
65
+ // ai-dev-team 디렉토리 생성
66
+ fs.ensureDirSync(path.join(workspace, 'roles'));
67
+ fs.ensureDirSync(path.join(workspace, 'artifacts'));
68
+ fs.ensureDirSync(path.join(workspace, 'rules'));
69
+ fs.ensureDirSync(path.join(workspace, 'artifacts', 'features', '_template'));
70
+ fs.ensureDirSync(path.join(workspace, 'artifacts', 'rfc'));
71
+
72
+ // Core 복사
73
+ console.log(chalk.gray('📁 Core 파일 복사 중...'));
74
+ copyDirMerge(path.join(coreDir, 'roles'), path.join(workspace, 'roles'));
75
+ copyDirMerge(path.join(coreDir, 'artifacts'), path.join(workspace, 'artifacts'));
76
+ copyDirMerge(path.join(coreDir, 'rules'), path.join(workspace, 'rules'));
77
+
78
+ // Template 복사 (머지)
79
+ console.log(chalk.gray(`📁 ${template} 템플릿 복사 중...`));
80
+ copyDirMerge(path.join(templateDir, 'roles'), path.join(workspace, 'roles'));
81
+ copyDirMerge(path.join(templateDir, 'artifacts'), path.join(workspace, 'artifacts'));
82
+ copyDirMerge(path.join(templateDir, 'rules'), path.join(workspace, 'rules'));
83
+
84
+ // Feature 템플릿 복사
85
+ const featureTemplateDir = path.join(packageRoot, 'ai-dev-team', 'artifacts', 'features', '_template');
86
+ if (fs.existsSync(featureTemplateDir)) {
87
+ copyDirMerge(featureTemplateDir, path.join(workspace, 'artifacts', 'features', '_template'));
88
+ }
89
+
90
+ // RFC 템플릿 복사
91
+ const rfcTemplateFile = path.join(packageRoot, 'ai-dev-team', 'artifacts', 'rfc', 'RFC-0000-template.md');
92
+ if (fs.existsSync(rfcTemplateFile)) {
93
+ fs.copyFileSync(rfcTemplateFile, path.join(workspace, 'artifacts', 'rfc', 'RFC-0000-template.md'));
94
+ }
95
+
96
+ // 현재 템플릿 저장
97
+ fs.writeFileSync(getCurrentTemplateFile(), template);
98
+
99
+ // 결과 출력
100
+ console.log('');
101
+ console.log(chalk.green('✅ 세팅 완료!'));
102
+ console.log('');
103
+
104
+ // 세팅된 파일 목록
105
+ const roles = fs.readdirSync(path.join(workspace, 'roles')).filter(f => f.endsWith('.md'));
106
+ const artifacts = fs.readdirSync(path.join(workspace, 'artifacts')).filter(f => f.endsWith('.md'));
107
+ const rules = fs.readdirSync(path.join(workspace, 'rules')).filter(f => f.endsWith('.md'));
108
+
109
+ console.log(chalk.white.bold('📂 ai-dev-team/'));
110
+ console.log(chalk.gray(` roles/ ${roles.length}개 역할`));
111
+ console.log(chalk.gray(` artifacts/ ${artifacts.length}개 산출물`));
112
+ console.log(chalk.gray(` rules/ ${rules.length}개 규칙`));
113
+ console.log('');
114
+ console.log(chalk.cyan('다음 단계:'));
115
+ console.log(chalk.gray(' ada status # 상태 확인'));
116
+ console.log(chalk.gray(' ada planner claude # AI 에이전트 실행'));
117
+ console.log('');
118
+ }
119
+
120
+ function getTemplateDescription(template) {
121
+ const descriptions = {
122
+ 'web-dev': 'web-dev - 웹 서비스 개발 (Backend + Frontend)',
123
+ 'library': 'library - 라이브러리/SDK 개발',
124
+ 'game': 'game - 게임 개발',
125
+ 'cli': 'cli - CLI 도구 개발'
126
+ };
127
+ return descriptions[template] || template;
128
+ }
@@ -0,0 +1,76 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+ import {
5
+ getWorkspaceDir,
6
+ getCurrentTemplate,
7
+ isWorkspaceSetup
8
+ } from '../utils/files.js';
9
+
10
+ export async function status() {
11
+ const workspace = getWorkspaceDir();
12
+
13
+ console.log('');
14
+ console.log(chalk.cyan('━'.repeat(50)));
15
+ console.log(chalk.cyan.bold('📊 현재 상태'));
16
+ console.log(chalk.cyan('━'.repeat(50)));
17
+ console.log('');
18
+
19
+ if (!isWorkspaceSetup()) {
20
+ console.log(chalk.yellow('⚠️ 세팅되지 않음'));
21
+ console.log('');
22
+ console.log(chalk.gray('세팅하려면:'));
23
+ console.log(chalk.white(' ada setup'));
24
+ console.log('');
25
+ return;
26
+ }
27
+
28
+ const template = getCurrentTemplate();
29
+ const rolesDir = path.join(workspace, 'roles');
30
+ const artifactsDir = path.join(workspace, 'artifacts');
31
+ const rulesDir = path.join(workspace, 'rules');
32
+
33
+ // 템플릿 정보
34
+ console.log(chalk.white.bold('템플릿:'), chalk.green(template || '알 수 없음'));
35
+ console.log('');
36
+
37
+ // 역할 목록
38
+ const roles = fs.readdirSync(rolesDir).filter(f => f.endsWith('.md'));
39
+ console.log(chalk.white.bold('역할 (Roles):'));
40
+ roles.forEach(r => {
41
+ console.log(chalk.gray(` • ${r.replace('.md', '')}`));
42
+ });
43
+ console.log('');
44
+
45
+ // 산출물 목록
46
+ const artifacts = fs.readdirSync(artifactsDir).filter(f => f.endsWith('.md'));
47
+ console.log(chalk.white.bold('산출물 (Artifacts):'));
48
+ artifacts.forEach(a => {
49
+ const filePath = path.join(artifactsDir, a);
50
+ const content = fs.readFileSync(filePath, 'utf-8');
51
+ const status = getDocumentStatus(content);
52
+ console.log(chalk.gray(` • ${a.replace('.md', '')} ${status}`));
53
+ });
54
+ console.log('');
55
+
56
+ // 규칙 목록
57
+ const rules = fs.readdirSync(rulesDir).filter(f => f.endsWith('.md'));
58
+ console.log(chalk.white.bold('규칙 (Rules):'));
59
+ rules.forEach(r => {
60
+ console.log(chalk.gray(` • ${r.replace('.md', '')}`));
61
+ });
62
+ console.log('');
63
+ }
64
+
65
+ function getDocumentStatus(content) {
66
+ if (content.includes('Frozen') || content.includes('🔒')) {
67
+ return chalk.blue('[Frozen]');
68
+ }
69
+ if (content.includes('Confirmed') || content.includes('확정')) {
70
+ return chalk.green('[Confirmed]');
71
+ }
72
+ if (content.includes('Draft') || content.includes('초안')) {
73
+ return chalk.yellow('[Draft]');
74
+ }
75
+ return '';
76
+ }
@@ -0,0 +1,219 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+ import { getWorkspaceDir, isWorkspaceSetup } from '../utils/files.js';
5
+
6
+ export async function validate(doc) {
7
+ if (!isWorkspaceSetup()) {
8
+ console.log(chalk.red('❌ 먼저 setup을 실행하세요.'));
9
+ process.exit(1);
10
+ }
11
+
12
+ const workspace = getWorkspaceDir();
13
+ const artifactsDir = path.join(workspace, 'artifacts');
14
+
15
+ console.log('');
16
+ console.log(chalk.cyan('━'.repeat(50)));
17
+ console.log(chalk.cyan.bold('📋 문서 검증'));
18
+ console.log(chalk.cyan('━'.repeat(50)));
19
+ console.log('');
20
+
21
+ let totalPass = 0;
22
+ let totalFail = 0;
23
+ let totalWarn = 0;
24
+
25
+ const validators = {
26
+ plan: validatePlan,
27
+ project: validateProject,
28
+ backlog: validateBacklog,
29
+ sprint: validateSprint
30
+ };
31
+
32
+ if (doc && validators[doc]) {
33
+ // 특정 문서만 검증
34
+ const result = validators[doc](artifactsDir);
35
+ totalPass += result.pass;
36
+ totalFail += result.fail;
37
+ totalWarn += result.warn;
38
+ } else {
39
+ // 전체 검증
40
+ for (const [name, validator] of Object.entries(validators)) {
41
+ const result = validator(artifactsDir);
42
+ totalPass += result.pass;
43
+ totalFail += result.fail;
44
+ totalWarn += result.warn;
45
+ }
46
+ }
47
+
48
+ // 결과 요약
49
+ console.log('');
50
+ console.log(chalk.cyan('━'.repeat(50)));
51
+ console.log(chalk.white.bold('📊 검증 결과'));
52
+ console.log(chalk.cyan('━'.repeat(50)));
53
+ console.log('');
54
+ console.log(chalk.green(` ✓ PASS: ${totalPass}`));
55
+ console.log(chalk.red(` ✗ FAIL: ${totalFail}`));
56
+ console.log(chalk.yellow(` ⚠ WARN: ${totalWarn}`));
57
+ console.log('');
58
+
59
+ if (totalFail > 0) {
60
+ process.exit(1);
61
+ }
62
+ }
63
+
64
+ function validatePlan(artifactsDir) {
65
+ const filePath = path.join(artifactsDir, 'plan.md');
66
+ let pass = 0, fail = 0, warn = 0;
67
+
68
+ console.log(chalk.white.bold('📄 plan.md'));
69
+
70
+ if (!fs.existsSync(filePath)) {
71
+ console.log(chalk.red(' ✗ 파일 없음'));
72
+ return { pass: 0, fail: 1, warn: 0 };
73
+ }
74
+
75
+ const content = fs.readFileSync(filePath, 'utf-8');
76
+
77
+ // 필수 섹션 검사
78
+ const requiredSections = ['서비스 개요', '기능 목록', '비기능 요구사항'];
79
+ for (const section of requiredSections) {
80
+ if (content.includes(section)) {
81
+ console.log(chalk.green(` ✓ 섹션 존재: ${section}`));
82
+ pass++;
83
+ } else {
84
+ console.log(chalk.red(` ✗ 섹션 누락: ${section}`));
85
+ fail++;
86
+ }
87
+ }
88
+
89
+ // TBD 개수 검사
90
+ const tbdMatches = content.match(/TBD/gi) || [];
91
+ if (tbdMatches.length > 3) {
92
+ console.log(chalk.yellow(` ⚠ TBD 항목: ${tbdMatches.length}개 (3개 초과)`));
93
+ warn++;
94
+ } else {
95
+ console.log(chalk.green(` ✓ TBD 항목: ${tbdMatches.length}개`));
96
+ pass++;
97
+ }
98
+
99
+ console.log('');
100
+ return { pass, fail, warn };
101
+ }
102
+
103
+ function validateProject(artifactsDir) {
104
+ const filePath = path.join(artifactsDir, 'project.md');
105
+ let pass = 0, fail = 0, warn = 0;
106
+
107
+ console.log(chalk.white.bold('📄 project.md'));
108
+
109
+ if (!fs.existsSync(filePath)) {
110
+ console.log(chalk.red(' ✗ 파일 없음'));
111
+ return { pass: 0, fail: 1, warn: 0 };
112
+ }
113
+
114
+ const content = fs.readFileSync(filePath, 'utf-8');
115
+
116
+ // 필수 섹션 검사
117
+ const requiredSections = ['프로젝트 규모', '기술 스택'];
118
+ for (const section of requiredSections) {
119
+ if (content.includes(section)) {
120
+ console.log(chalk.green(` ✓ 섹션 존재: ${section}`));
121
+ pass++;
122
+ } else {
123
+ console.log(chalk.red(` ✗ 섹션 누락: ${section}`));
124
+ fail++;
125
+ }
126
+ }
127
+
128
+ // Frozen 상태 검사
129
+ if (content.includes('Frozen') || content.includes('🔒')) {
130
+ console.log(chalk.green(' ✓ Frozen 상태 표시됨'));
131
+ pass++;
132
+ } else {
133
+ console.log(chalk.yellow(' ⚠ Frozen 상태 미표시'));
134
+ warn++;
135
+ }
136
+
137
+ // 모호한 버전 검사 (예: 1.x, 2.x)
138
+ if (/\d+\.x/i.test(content)) {
139
+ console.log(chalk.yellow(' ⚠ 모호한 버전 형식 (예: 1.x)'));
140
+ warn++;
141
+ } else {
142
+ console.log(chalk.green(' ✓ 버전 형식 양호'));
143
+ pass++;
144
+ }
145
+
146
+ console.log('');
147
+ return { pass, fail, warn };
148
+ }
149
+
150
+ function validateBacklog(artifactsDir) {
151
+ const filePath = path.join(artifactsDir, 'backlog.md');
152
+ let pass = 0, fail = 0, warn = 0;
153
+
154
+ console.log(chalk.white.bold('📄 backlog.md'));
155
+
156
+ if (!fs.existsSync(filePath)) {
157
+ console.log(chalk.red(' ✗ 파일 없음'));
158
+ return { pass: 0, fail: 1, warn: 0 };
159
+ }
160
+
161
+ const content = fs.readFileSync(filePath, 'utf-8');
162
+
163
+ // Task 개수 검사
164
+ const taskMatches = content.match(/TASK-\d+/gi) || [];
165
+ if (taskMatches.length > 0) {
166
+ console.log(chalk.green(` ✓ Task 개수: ${taskMatches.length}개`));
167
+ pass++;
168
+ } else {
169
+ console.log(chalk.red(' ✗ Task 없음'));
170
+ fail++;
171
+ }
172
+
173
+ // 수용 조건 존재 검사
174
+ if (content.includes('수용 조건') || content.includes('AC-') || content.includes('Acceptance')) {
175
+ console.log(chalk.green(' ✓ 수용 조건 존재'));
176
+ pass++;
177
+ } else {
178
+ console.log(chalk.yellow(' ⚠ 수용 조건 미확인'));
179
+ warn++;
180
+ }
181
+
182
+ console.log('');
183
+ return { pass, fail, warn };
184
+ }
185
+
186
+ function validateSprint(artifactsDir) {
187
+ const filePath = path.join(artifactsDir, 'current-sprint.md');
188
+ let pass = 0, fail = 0, warn = 0;
189
+
190
+ console.log(chalk.white.bold('📄 current-sprint.md'));
191
+
192
+ if (!fs.existsSync(filePath)) {
193
+ console.log(chalk.gray(' - 파일 없음 (스프린트 시작 전)'));
194
+ return { pass: 0, fail: 0, warn: 0 };
195
+ }
196
+
197
+ const content = fs.readFileSync(filePath, 'utf-8');
198
+
199
+ // 스프린트 번호 검사
200
+ if (/Sprint\s*#?\d+/i.test(content) || /스프린트\s*#?\d+/.test(content)) {
201
+ console.log(chalk.green(' ✓ 스프린트 번호 존재'));
202
+ pass++;
203
+ } else {
204
+ console.log(chalk.yellow(' ⚠ 스프린트 번호 미확인'));
205
+ warn++;
206
+ }
207
+
208
+ // 목표 섹션 검사
209
+ if (content.includes('목표') || content.includes('Goal')) {
210
+ console.log(chalk.green(' ✓ 목표 섹션 존재'));
211
+ pass++;
212
+ } else {
213
+ console.log(chalk.yellow(' ⚠ 목표 섹션 미확인'));
214
+ warn++;
215
+ }
216
+
217
+ console.log('');
218
+ return { pass, fail, warn };
219
+ }
package/src/index.js ADDED
@@ -0,0 +1,12 @@
1
+ // Commands
2
+ export { setup } from './commands/setup.js';
3
+ export { status } from './commands/status.js';
4
+ export { reset } from './commands/reset.js';
5
+ export { validate } from './commands/validate.js';
6
+ export { sessions } from './commands/sessions.js';
7
+ export { logs } from './commands/logs.js';
8
+ export { run } from './commands/run.js';
9
+ export { interactive } from './commands/interactive.js';
10
+
11
+ // Utils
12
+ export * from './utils/files.js';
@@ -0,0 +1,134 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+
8
+ /**
9
+ * 패키지 루트 디렉토리 반환
10
+ */
11
+ export function getPackageRoot() {
12
+ return path.resolve(__dirname, '../..');
13
+ }
14
+
15
+ /**
16
+ * 작업 디렉토리 (ai-dev-team) 경로 반환
17
+ */
18
+ export function getWorkspaceDir() {
19
+ return path.join(process.cwd(), 'ai-dev-team');
20
+ }
21
+
22
+ /**
23
+ * 세션 디렉토리 경로 반환
24
+ */
25
+ export function getSessionsDir() {
26
+ return path.join(getWorkspaceDir(), '.sessions');
27
+ }
28
+
29
+ /**
30
+ * 로그 디렉토리 경로 반환
31
+ */
32
+ export function getLogsDir() {
33
+ return path.join(process.cwd(), 'logs');
34
+ }
35
+
36
+ /**
37
+ * 현재 템플릿 파일 경로 반환
38
+ */
39
+ export function getCurrentTemplateFile() {
40
+ return path.join(getWorkspaceDir(), '.current-template');
41
+ }
42
+
43
+ /**
44
+ * 현재 세팅된 템플릿 반환
45
+ */
46
+ export function getCurrentTemplate() {
47
+ const templateFile = getCurrentTemplateFile();
48
+ if (fs.existsSync(templateFile)) {
49
+ return fs.readFileSync(templateFile, 'utf-8').trim();
50
+ }
51
+ return null;
52
+ }
53
+
54
+ /**
55
+ * 세션 ID 생성
56
+ */
57
+ export function generateSessionId() {
58
+ const now = new Date();
59
+ const date = now.toISOString().slice(0, 10).replace(/-/g, '');
60
+ const time = now.toTimeString().slice(0, 8).replace(/:/g, '');
61
+ const random = Math.random().toString(16).slice(2, 10);
62
+ return `${date}-${time}-${random}`;
63
+ }
64
+
65
+ /**
66
+ * 타임스탬프 생성
67
+ */
68
+ export function getTimestamp() {
69
+ return new Date().toISOString().replace('T', ' ').slice(0, 19);
70
+ }
71
+
72
+ /**
73
+ * 사용 가능한 템플릿 목록
74
+ */
75
+ export function getAvailableTemplates() {
76
+ const templatesDir = path.join(getPackageRoot(), 'templates');
77
+ if (!fs.existsSync(templatesDir)) {
78
+ return [];
79
+ }
80
+ return fs.readdirSync(templatesDir).filter(f => {
81
+ return fs.statSync(path.join(templatesDir, f)).isDirectory();
82
+ });
83
+ }
84
+
85
+ /**
86
+ * 사용 가능한 역할 목록
87
+ */
88
+ export function getAvailableRoles() {
89
+ const workspaceRoles = path.join(getWorkspaceDir(), 'roles');
90
+ if (!fs.existsSync(workspaceRoles)) {
91
+ return [];
92
+ }
93
+ return fs.readdirSync(workspaceRoles)
94
+ .filter(f => f.endsWith('.md'))
95
+ .map(f => f.replace('.md', ''));
96
+ }
97
+
98
+ /**
99
+ * 사용 가능한 도구 목록
100
+ */
101
+ export function getAvailableTools() {
102
+ return ['claude', 'codex', 'gemini', 'copilot'];
103
+ }
104
+
105
+ /**
106
+ * 워크스페이스가 세팅되어 있는지 확인
107
+ */
108
+ export function isWorkspaceSetup() {
109
+ const workspace = getWorkspaceDir();
110
+ const rolesDir = path.join(workspace, 'roles');
111
+ return fs.existsSync(rolesDir) && fs.readdirSync(rolesDir).filter(f => f.endsWith('.md')).length > 0;
112
+ }
113
+
114
+ /**
115
+ * 디렉토리 복사 (머지)
116
+ */
117
+ export function copyDirMerge(src, dest) {
118
+ if (!fs.existsSync(src)) return;
119
+
120
+ fs.ensureDirSync(dest);
121
+ const items = fs.readdirSync(src);
122
+
123
+ for (const item of items) {
124
+ const srcPath = path.join(src, item);
125
+ const destPath = path.join(dest, item);
126
+ const stat = fs.statSync(srcPath);
127
+
128
+ if (stat.isDirectory()) {
129
+ copyDirMerge(srcPath, destPath);
130
+ } else {
131
+ fs.copyFileSync(srcPath, destPath);
132
+ }
133
+ }
134
+ }