@silbaram/artifact-driven-agent 0.1.9 → 0.2.3
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 +296 -25
- package/ai-dev-team/.ada-status.template.json +10 -10
- package/ai-dev-team/README.md +39 -44
- package/ai-dev-team/artifacts/api.md +2 -212
- package/ai-dev-team/artifacts/features/_template/api.md +19 -19
- package/ai-dev-team/artifacts/features/_template/review.md +14 -14
- package/ai-dev-team/artifacts/features/_template/spec.md +28 -28
- package/ai-dev-team/artifacts/features/_template/ui.md +14 -14
- package/ai-dev-team/artifacts/improvement-reports/IMP-0000-template.md +57 -57
- package/ai-dev-team/artifacts/project.md +2 -193
- package/ai-dev-team/artifacts/rfc/RFC-0000-template.md +49 -49
- package/core/artifacts/decision.md +72 -72
- package/core/artifacts/plan.md +187 -187
- package/core/artifacts/project.md +193 -193
- package/core/artifacts/sprints/_template/docs/release-notes.md +37 -37
- package/core/artifacts/sprints/_template/meta.md +54 -54
- package/core/artifacts/sprints/_template/retrospective.md +50 -50
- package/core/artifacts/sprints/_template/review-reports/review-template.md +49 -49
- package/core/artifacts/sprints/_template/tasks/task-template.md +43 -43
- package/core/docs-templates/mkdocs/docs/architecture/overview.md +29 -29
- package/core/docs-templates/mkdocs/docs/changelog.md +36 -36
- package/core/docs-templates/mkdocs/docs/contributing/contributing.md +60 -60
- package/core/docs-templates/mkdocs/docs/getting-started/configuration.md +51 -51
- package/core/docs-templates/mkdocs/docs/getting-started/installation.md +41 -41
- package/core/docs-templates/mkdocs/docs/getting-started/quick-start.md +56 -56
- package/core/docs-templates/mkdocs/docs/guides/api-reference.md +83 -83
- package/core/docs-templates/mkdocs/docs/index.md +32 -32
- package/core/docs-templates/mkdocs/mkdocs.yml +86 -86
- package/core/roles/analyzer.md +265 -265
- package/core/roles/developer.md +6 -1
- package/core/roles/documenter.md +226 -715
- package/core/roles/improver.md +461 -461
- package/core/roles/manager.md +574 -544
- package/core/roles/planner.md +398 -398
- package/core/roles/reviewer.md +294 -294
- package/core/rules/document-priority.md +198 -198
- package/core/rules/escalation.md +172 -172
- package/core/rules/iteration.md +236 -236
- package/core/rules/rfc.md +31 -31
- package/core/rules/rollback.md +218 -218
- package/core/skills/_template/SKILL.md +50 -0
- package/core/skills/clean-code/SKILL.md +177 -0
- package/core/skills/code-review-checklist/SKILL.md +125 -0
- package/core/skills/error-handling/SKILL.md +155 -0
- package/core/skills/talk-game-dot-design/SKILL.md +85 -0
- package/core/skills/talk-game-dot-design/references/asset-specs.md +57 -0
- package/core/skills/talk-game-dot-design/references/review-checklist.md +34 -0
- package/core/skills/task-writing/SKILL.md +166 -0
- package/core/skills/testing-patterns/SKILL.md +140 -0
- package/dist/bin/cli.d.ts +2 -0
- package/dist/bin/cli.js +121 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/src/commands/config.d.ts +4 -0
- package/dist/src/commands/config.js +427 -0
- package/dist/src/commands/config.js.map +1 -0
- package/dist/src/commands/docs.d.ts +12 -0
- package/dist/src/commands/docs.js +453 -0
- package/dist/src/commands/docs.js.map +1 -0
- package/dist/src/commands/interactive.d.ts +4 -0
- package/dist/src/commands/interactive.js +380 -0
- package/dist/src/commands/interactive.js.map +1 -0
- package/dist/src/commands/logs.d.ts +1 -0
- package/dist/src/commands/logs.js +76 -0
- package/dist/src/commands/logs.js.map +1 -0
- package/dist/src/commands/monitor.d.ts +6 -0
- package/dist/src/commands/monitor.js +216 -0
- package/dist/src/commands/monitor.js.map +1 -0
- package/dist/src/commands/reset.d.ts +4 -0
- package/dist/src/commands/reset.js +57 -0
- package/dist/src/commands/reset.js.map +1 -0
- package/dist/src/commands/run.d.ts +45 -0
- package/dist/src/commands/run.js +421 -0
- package/dist/src/commands/run.js.map +1 -0
- package/dist/src/commands/sessions.d.ts +5 -0
- package/dist/src/commands/sessions.js +563 -0
- package/dist/src/commands/sessions.js.map +1 -0
- package/dist/src/commands/setup.d.ts +1 -0
- package/dist/src/commands/setup.js +132 -0
- package/dist/src/commands/setup.js.map +1 -0
- package/dist/src/commands/skills.d.ts +4 -0
- package/dist/src/commands/skills.js +748 -0
- package/dist/src/commands/skills.js.map +1 -0
- package/dist/src/commands/sprint.d.ts +4 -0
- package/dist/src/commands/sprint.js +434 -0
- package/dist/src/commands/sprint.js.map +1 -0
- package/dist/src/commands/status.d.ts +1 -0
- package/dist/src/commands/status.js +97 -0
- package/dist/src/commands/status.js.map +1 -0
- package/dist/src/commands/upgrade.d.ts +6 -0
- package/dist/src/commands/upgrade.js +350 -0
- package/dist/src/commands/upgrade.js.map +1 -0
- package/dist/src/commands/validate.d.ts +8 -0
- package/dist/src/commands/validate.js +260 -0
- package/dist/src/commands/validate.js.map +1 -0
- package/dist/src/commands/validate.test.d.ts +1 -0
- package/dist/src/commands/validate.test.js +74 -0
- package/dist/src/commands/validate.test.js.map +1 -0
- package/{src/index.js → dist/src/index.d.ts} +1 -3
- package/dist/src/index.js +15 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/types/common.d.ts +31 -0
- package/dist/src/types/common.js +2 -0
- package/dist/src/types/common.js.map +1 -0
- package/dist/src/types/config.d.ts +46 -0
- package/dist/src/types/config.js +2 -0
- package/dist/src/types/config.js.map +1 -0
- package/dist/src/types/index.d.ts +53 -0
- package/dist/src/types/index.js +2 -0
- package/dist/src/types/index.js.map +1 -0
- package/dist/src/types/session.d.ts +87 -0
- package/dist/src/types/session.js +2 -0
- package/dist/src/types/session.js.map +1 -0
- package/dist/src/types/task.d.ts +32 -0
- package/dist/src/types/task.js +2 -0
- package/dist/src/types/task.js.map +1 -0
- package/dist/src/ui/dashboard.d.ts +9 -0
- package/dist/src/ui/dashboard.js +468 -0
- package/dist/src/ui/dashboard.js.map +1 -0
- package/dist/src/ui/keyHandler.d.ts +47 -0
- package/dist/src/ui/keyHandler.js +132 -0
- package/dist/src/ui/keyHandler.js.map +1 -0
- package/dist/src/ui/quickActions.d.ts +25 -0
- package/dist/src/ui/quickActions.js +106 -0
- package/dist/src/ui/quickActions.js.map +1 -0
- package/dist/src/utils/config.d.ts +33 -0
- package/dist/src/utils/config.js +135 -0
- package/dist/src/utils/config.js.map +1 -0
- package/dist/src/utils/files.d.ts +84 -0
- package/dist/src/utils/files.js +202 -0
- package/dist/src/utils/files.js.map +1 -0
- package/dist/src/utils/promptBuilder.d.ts +39 -0
- package/dist/src/utils/promptBuilder.js +726 -0
- package/dist/src/utils/promptBuilder.js.map +1 -0
- package/dist/src/utils/sessionState.d.ts +83 -0
- package/dist/src/utils/sessionState.js +466 -0
- package/dist/src/utils/sessionState.js.map +1 -0
- package/dist/src/utils/sessionState.process.test.d.ts +1 -0
- package/dist/src/utils/sessionState.process.test.js +93 -0
- package/dist/src/utils/sessionState.process.test.js.map +1 -0
- package/dist/src/utils/sessionState.test.d.ts +1 -0
- package/dist/src/utils/sessionState.test.js +150 -0
- package/dist/src/utils/sessionState.test.js.map +1 -0
- package/dist/src/utils/sprintUtils.d.ts +13 -0
- package/dist/src/utils/sprintUtils.js +129 -0
- package/dist/src/utils/sprintUtils.js.map +1 -0
- package/dist/src/utils/taskParser.d.ts +9 -0
- package/dist/src/utils/taskParser.js +122 -0
- package/dist/src/utils/taskParser.js.map +1 -0
- package/dist/src/utils/taskParser.test.d.ts +1 -0
- package/dist/src/utils/taskParser.test.js +69 -0
- package/dist/src/utils/taskParser.test.js.map +1 -0
- package/package.json +62 -47
- package/templates/cli/artifacts/commands.md +262 -262
- package/templates/cli/artifacts/output-format.md +298 -298
- package/templates/cli/rules/command-change.md +225 -225
- package/templates/game/artifacts/assets.md +148 -148
- package/templates/game/artifacts/game-systems.md +217 -217
- package/templates/game/artifacts/hud.md +199 -199
- package/templates/game/rules/system-change.md +184 -184
- package/templates/library/artifacts/changelog.md +84 -84
- package/templates/library/artifacts/examples.md +157 -157
- package/templates/library/artifacts/public-api.md +197 -197
- package/templates/library/rules/versioning.md +186 -186
- package/templates/web-dev/artifacts/api.md +212 -212
- package/templates/web-dev/artifacts/ui.md +104 -104
- package/templates/web-dev/rules/api-change.md +198 -198
- package/ai-dev-team/.ada-status.json +0 -10
- package/ai-dev-team/.ada-version +0 -6
- package/ai-dev-team/.current-template +0 -1
- package/ai-dev-team/.gitkeep +0 -0
- package/ai-dev-team/.sessions/logs/20260124-014551-00f04724.log +0 -5
- package/ai-dev-team/.sessions/logs/20260124-014623-cb2b1d44.log +0 -5
- package/ai-dev-team/artifacts/.gitkeep +0 -0
- package/ai-dev-team/artifacts/decision.md +0 -72
- package/ai-dev-team/artifacts/plan.md +0 -187
- package/ai-dev-team/artifacts/sprints/_template/docs/release-notes.md +0 -37
- package/ai-dev-team/artifacts/sprints/_template/meta.md +0 -54
- package/ai-dev-team/artifacts/sprints/_template/retrospective.md +0 -50
- package/ai-dev-team/artifacts/sprints/_template/review-reports/review-template.md +0 -49
- package/ai-dev-team/artifacts/sprints/_template/tasks/task-template.md +0 -43
- package/ai-dev-team/artifacts/ui.md +0 -104
- package/ai-dev-team/roles/.gitkeep +0 -0
- package/ai-dev-team/roles/analyzer.md +0 -265
- package/ai-dev-team/roles/developer.md +0 -222
- package/ai-dev-team/roles/documenter.md +0 -715
- package/ai-dev-team/roles/improver.md +0 -461
- package/ai-dev-team/roles/manager.md +0 -544
- package/ai-dev-team/roles/planner.md +0 -398
- package/ai-dev-team/roles/reviewer.md +0 -294
- package/ai-dev-team/rules/.gitkeep +0 -0
- package/ai-dev-team/rules/api-change.md +0 -198
- package/ai-dev-team/rules/document-priority.md +0 -199
- package/ai-dev-team/rules/escalation.md +0 -172
- package/ai-dev-team/rules/iteration.md +0 -236
- package/ai-dev-team/rules/rfc.md +0 -31
- package/ai-dev-team/rules/rollback.md +0 -218
- package/bin/cli.js +0 -127
- package/src/commands/config.js +0 -371
- package/src/commands/docs.js +0 -502
- package/src/commands/interactive.js +0 -392
- package/src/commands/logs.js +0 -81
- package/src/commands/monitor.js +0 -236
- package/src/commands/reset.js +0 -66
- package/src/commands/run.js +0 -739
- package/src/commands/sessions.js +0 -646
- package/src/commands/setup.js +0 -149
- package/src/commands/sprint.js +0 -503
- package/src/commands/status.js +0 -109
- package/src/commands/upgrade.js +0 -416
- package/src/commands/validate.js +0 -289
- package/src/commands/validate.test.js +0 -84
- package/src/ui/dashboard.js +0 -518
- package/src/ui/keyHandler.js +0 -147
- package/src/ui/quickActions.js +0 -111
- package/src/utils/config.js +0 -74
- package/src/utils/files.js +0 -201
- package/src/utils/sessionState.js +0 -513
- package/src/utils/sessionState.process.test.js +0 -101
- package/src/utils/sessionState.test.js +0 -183
- package/src/utils/sprintUtils.js +0 -134
- package/src/utils/taskParser.js +0 -134
- package/src/utils/taskParser.test.js +0 -76
package/src/commands/run.js
DELETED
|
@@ -1,739 +0,0 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { spawn } from 'child_process';
|
|
4
|
-
import chalk from 'chalk';
|
|
5
|
-
import inquirer from 'inquirer';
|
|
6
|
-
import {
|
|
7
|
-
getWorkspaceDir,
|
|
8
|
-
getSessionsDir,
|
|
9
|
-
getLogsDir,
|
|
10
|
-
getCurrentTemplate,
|
|
11
|
-
getAvailableRoles,
|
|
12
|
-
generateSessionId,
|
|
13
|
-
getTimestamp,
|
|
14
|
-
isWorkspaceSetup
|
|
15
|
-
} from '../utils/files.js';
|
|
16
|
-
import {
|
|
17
|
-
registerSession,
|
|
18
|
-
unregisterSession,
|
|
19
|
-
updateSessionDetails,
|
|
20
|
-
getActiveSessions,
|
|
21
|
-
getPendingQuestions
|
|
22
|
-
} from '../utils/sessionState.js';
|
|
23
|
-
import { getToolForRole } from '../utils/config.js';
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* [API] AI 에이전트 세션 실행 (핵심 로직)
|
|
27
|
-
* @param {string} role - 실행할 역할 (예: 'developer')
|
|
28
|
-
* @param {string} tool - 사용할 도구 (예: 'claude')
|
|
29
|
-
* @param {object} options - 추가 옵션
|
|
30
|
-
* @returns {Promise<object>} 세션 결과 정보
|
|
31
|
-
*/
|
|
32
|
-
export async function executeAgentSession(role, tool, options = {}) {
|
|
33
|
-
const roles = getAvailableRoles();
|
|
34
|
-
const tools = ['claude', 'codex', 'gemini', 'copilot'];
|
|
35
|
-
|
|
36
|
-
// 역할 검증
|
|
37
|
-
if (!roles.includes(role)) {
|
|
38
|
-
throw new Error(`알 수 없는 역할입니다: ${role} (사용 가능: ${roles.join(', ')})`);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// 도구 검증
|
|
42
|
-
if (!tools.includes(tool)) {
|
|
43
|
-
throw new Error(`알 수 없는 도구입니다: ${tool} (사용 가능: ${tools.join(', ')})`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const workspace = getWorkspaceDir();
|
|
47
|
-
const template = getCurrentTemplate();
|
|
48
|
-
const sessionId = generateSessionId();
|
|
49
|
-
const sessionsDir = getSessionsDir();
|
|
50
|
-
const logsDir = getLogsDir();
|
|
51
|
-
|
|
52
|
-
// 세션 디렉토리 생성
|
|
53
|
-
const sessionDir = path.join(sessionsDir, sessionId);
|
|
54
|
-
fs.ensureDirSync(sessionDir);
|
|
55
|
-
fs.ensureDirSync(logsDir);
|
|
56
|
-
|
|
57
|
-
// 세션 정보 객체
|
|
58
|
-
const sessionInfo = {
|
|
59
|
-
session_id: sessionId,
|
|
60
|
-
role: role,
|
|
61
|
-
tool: tool,
|
|
62
|
-
template: template,
|
|
63
|
-
started_at: getTimestamp(),
|
|
64
|
-
status: 'active'
|
|
65
|
-
};
|
|
66
|
-
const sessionFile = path.join(sessionDir, 'session.json');
|
|
67
|
-
fs.writeFileSync(sessionFile, JSON.stringify(sessionInfo, null, 2));
|
|
68
|
-
|
|
69
|
-
// 로그 헬퍼
|
|
70
|
-
const logFile = path.join(logsDir, `${sessionId}.log`);
|
|
71
|
-
const logMessage = (level, msg) => {
|
|
72
|
-
const line = `[${getTimestamp()}] [${level}] ${msg}\n`;
|
|
73
|
-
fs.appendFileSync(logFile, line);
|
|
74
|
-
// 옵션에 따라 콘솔 출력 제어 가능 (현재는 항상 출력)
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
// 세션 정리 함수 (시그널 핸들러 및 정상 종료에서 공통 사용)
|
|
78
|
-
let isCleanedUp = false;
|
|
79
|
-
const cleanupSession = (status, reason = null) => {
|
|
80
|
-
if (isCleanedUp) return;
|
|
81
|
-
isCleanedUp = true;
|
|
82
|
-
|
|
83
|
-
sessionInfo.status = status;
|
|
84
|
-
sessionInfo.ended_at = getTimestamp();
|
|
85
|
-
if (reason) {
|
|
86
|
-
sessionInfo.termination_reason = reason;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
fs.writeFileSync(sessionFile, JSON.stringify(sessionInfo, null, 2));
|
|
91
|
-
logMessage('INFO', `세션 종료 (${status}): ${reason || '정상 종료'}`);
|
|
92
|
-
unregisterSession(sessionId);
|
|
93
|
-
} catch (err) {
|
|
94
|
-
// 정리 중 에러는 무시 (이미 종료 중)
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
// 시그널 핸들러 (Ctrl+C 등 강제 종료 시 세션 정리)
|
|
99
|
-
const handleSignal = (signal) => {
|
|
100
|
-
logMessage('INFO', `시그널 수신: ${signal}`);
|
|
101
|
-
cleanupSession('completed', `사용자 종료 (${signal})`);
|
|
102
|
-
|
|
103
|
-
// 핸들러 제거
|
|
104
|
-
process.removeListener('SIGINT', handleSignal);
|
|
105
|
-
process.removeListener('SIGTERM', handleSignal);
|
|
106
|
-
|
|
107
|
-
// exitOnSignal 옵션이 false가 아니면 프로세스 종료 (기본값: true)
|
|
108
|
-
if (options.exitOnSignal !== false) {
|
|
109
|
-
setTimeout(() => {
|
|
110
|
-
process.exit(0);
|
|
111
|
-
}, 100);
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
// 시그널 핸들러 등록
|
|
116
|
-
process.on('SIGINT', handleSignal);
|
|
117
|
-
process.on('SIGTERM', handleSignal);
|
|
118
|
-
|
|
119
|
-
try {
|
|
120
|
-
logMessage('INFO', `세션 시작: role=${role}, tool=${tool}, template=${template}`);
|
|
121
|
-
|
|
122
|
-
// 멀티 세션 등록
|
|
123
|
-
registerSession(sessionId, role, tool);
|
|
124
|
-
logMessage('INFO', `세션 등록: ${sessionId}`);
|
|
125
|
-
|
|
126
|
-
// 역할 파일 로드 (옵션으로 오버라이드 가능)
|
|
127
|
-
let systemPrompt;
|
|
128
|
-
if (options.systemPromptOverride) {
|
|
129
|
-
systemPrompt = options.systemPromptOverride;
|
|
130
|
-
logMessage('INFO', '시스템 프롬프트 오버라이드 사용됨');
|
|
131
|
-
} else {
|
|
132
|
-
const roleFile = path.join(workspace, 'roles', `${role}.md`);
|
|
133
|
-
if (!fs.existsSync(roleFile)) {
|
|
134
|
-
throw new Error(`역할 파일이 존재하지 않습니다: ${roleFile}`);
|
|
135
|
-
}
|
|
136
|
-
const roleContent = fs.readFileSync(roleFile, 'utf-8');
|
|
137
|
-
systemPrompt = buildSystemPrompt(workspace, role, roleContent);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// 프롬프트 파일 저장
|
|
141
|
-
const promptFile = path.join(sessionDir, 'system-prompt.md');
|
|
142
|
-
fs.writeFileSync(promptFile, systemPrompt, 'utf-8');
|
|
143
|
-
logMessage('INFO', `시스템 프롬프트 저장: ${promptFile}`);
|
|
144
|
-
|
|
145
|
-
// 터미널 UI 출력 (Headless 모드가 아닐 때만)
|
|
146
|
-
if (!options.headless) {
|
|
147
|
-
printSessionBanner(role, tool, sessionId, template);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// AI 도구 프로세스 실행
|
|
151
|
-
const handleSpawn = (child) => {
|
|
152
|
-
sessionInfo.pid = child.pid;
|
|
153
|
-
fs.writeFileSync(sessionFile, JSON.stringify(sessionInfo, null, 2));
|
|
154
|
-
updateSessionDetails(sessionId, { pid: child.pid });
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
const output = await launchTool(tool, systemPrompt, promptFile, logMessage, {
|
|
158
|
-
...options,
|
|
159
|
-
onSpawn: handleSpawn
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// 시그널 핸들러 제거
|
|
163
|
-
process.removeListener('SIGINT', handleSignal);
|
|
164
|
-
process.removeListener('SIGTERM', handleSignal);
|
|
165
|
-
|
|
166
|
-
// 정상 종료 처리
|
|
167
|
-
if (output) {
|
|
168
|
-
sessionInfo.output = output;
|
|
169
|
-
}
|
|
170
|
-
cleanupSession('completed');
|
|
171
|
-
|
|
172
|
-
// 캡처된 출력 반환
|
|
173
|
-
return { ...sessionInfo, output };
|
|
174
|
-
|
|
175
|
-
} catch (error) {
|
|
176
|
-
// 시그널 핸들러 제거
|
|
177
|
-
process.removeListener('SIGINT', handleSignal);
|
|
178
|
-
process.removeListener('SIGTERM', handleSignal);
|
|
179
|
-
|
|
180
|
-
// 에러가 사용자 종료(exit code 비정상)인 경우 completed로 처리
|
|
181
|
-
const isUserTermination = error.message && (
|
|
182
|
-
error.message.includes('exited with code 130') || // SIGINT
|
|
183
|
-
error.message.includes('exited with code 143') || // SIGTERM
|
|
184
|
-
error.message.includes('exited with code 1') // 일반 종료
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
if (isUserTermination) {
|
|
188
|
-
cleanupSession('completed', '사용자 종료');
|
|
189
|
-
return { ...sessionInfo };
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// 실제 에러 처리
|
|
193
|
-
sessionInfo.error = error.message;
|
|
194
|
-
cleanupSession('error', error.message);
|
|
195
|
-
logMessage('ERROR', error.message);
|
|
196
|
-
|
|
197
|
-
throw error;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* [CLI] 실행 명령어 핸들러
|
|
203
|
-
* 사용자 입력을 처리하고 executeAgentSession을 호출
|
|
204
|
-
*/
|
|
205
|
-
export async function runCommand(role, tool) {
|
|
206
|
-
if (!isWorkspaceSetup()) {
|
|
207
|
-
console.log(chalk.red('❌ 먼저 setup을 실행하세요.'));
|
|
208
|
-
console.log(chalk.gray(' ada setup'));
|
|
209
|
-
process.exit(1);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
try {
|
|
213
|
-
// 1. 역할 선택 (입력 없으면 질문)
|
|
214
|
-
if (!role) {
|
|
215
|
-
const roles = getAvailableRoles();
|
|
216
|
-
const answer = await inquirer.prompt([
|
|
217
|
-
{
|
|
218
|
-
type: 'list',
|
|
219
|
-
name: 'role',
|
|
220
|
-
message: '실행할 역할을 선택하세요:',
|
|
221
|
-
choices: roles
|
|
222
|
-
}
|
|
223
|
-
]);
|
|
224
|
-
role = answer.role;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// 2. 도구 자동 선택 (입력 없으면 설정 파일 참조)
|
|
228
|
-
if (!tool) {
|
|
229
|
-
tool = getToolForRole(role);
|
|
230
|
-
console.log(chalk.gray(`ℹ️ 설정된 기본 도구를 사용합니다: ${tool}`));
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// 3. 세션 실행
|
|
234
|
-
await executeAgentSession(role, tool);
|
|
235
|
-
|
|
236
|
-
} catch (error) {
|
|
237
|
-
console.error(chalk.red('\n❌ 실행 중 오류가 발생했습니다:'));
|
|
238
|
-
console.error(chalk.white(error.message));
|
|
239
|
-
process.exit(1);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// 기존 CLI 호환성을 위해 run이라는 이름으로 export
|
|
244
|
-
export { runCommand as run };
|
|
245
|
-
// 시스템 프롬프트 생성 로직도 외부에서 쓸 수 있게 export
|
|
246
|
-
export { buildSystemPrompt };
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
// ============================================================================
|
|
250
|
-
// 내부 헬퍼 함수들
|
|
251
|
-
// ============================================================================
|
|
252
|
-
|
|
253
|
-
function printSessionBanner(role, tool, sessionId, template) {
|
|
254
|
-
// 다른 활성 세션 정보
|
|
255
|
-
const activeSessions = getActiveSessions().filter(s => s.sessionId !== sessionId);
|
|
256
|
-
const pendingQuestions = getPendingQuestions();
|
|
257
|
-
|
|
258
|
-
// 터미널 타이틀
|
|
259
|
-
const terminalTitle = `ADA: ${role} (${tool})`;
|
|
260
|
-
process.stdout.write(`\x1b]0;${terminalTitle}\x07`);
|
|
261
|
-
|
|
262
|
-
console.log('');
|
|
263
|
-
console.log(chalk.cyan('━'.repeat(60)));
|
|
264
|
-
console.log(chalk.cyan.bold('🚀 AI 에이전트 실행'));
|
|
265
|
-
console.log(chalk.cyan('━'.repeat(60)));
|
|
266
|
-
console.log('');
|
|
267
|
-
|
|
268
|
-
const roleEmojis = {
|
|
269
|
-
'analyzer': '🔍',
|
|
270
|
-
'planner': '📋',
|
|
271
|
-
'improver': '🔧',
|
|
272
|
-
'architect': '🏛️',
|
|
273
|
-
'developer': '💻',
|
|
274
|
-
'backend': '⚙️',
|
|
275
|
-
'frontend': '🎨',
|
|
276
|
-
'reviewer': '👀',
|
|
277
|
-
'manager': '👔',
|
|
278
|
-
'library-developer': '📚',
|
|
279
|
-
'game-logic': '🎮',
|
|
280
|
-
'rendering': '🎬',
|
|
281
|
-
'cli-developer': '⌨️'
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
const roleEmoji = roleEmojis[role] || '🤖';
|
|
285
|
-
console.log(chalk.bgCyan.black.bold(` ${roleEmoji} 역할: ${role.toUpperCase()} `));
|
|
286
|
-
console.log('');
|
|
287
|
-
|
|
288
|
-
console.log(chalk.white(` 세션 ID: ${chalk.yellow(sessionId)}`));
|
|
289
|
-
console.log(chalk.white(` 템플릿: ${chalk.green(template)}`));
|
|
290
|
-
console.log(chalk.white(` 도구: ${chalk.green(tool)}`));
|
|
291
|
-
console.log(chalk.white(` 작업공간: ${chalk.gray('ai-dev-team/')}`));
|
|
292
|
-
console.log(chalk.white(` 로그: ${chalk.gray('.sessions/logs/' + sessionId + '.log')}`));
|
|
293
|
-
console.log('');
|
|
294
|
-
|
|
295
|
-
if (activeSessions.length > 0) {
|
|
296
|
-
console.log(chalk.white(` 🔗 활성 세션: ${chalk.yellow(activeSessions.length)}개`));
|
|
297
|
-
activeSessions.forEach(s => {
|
|
298
|
-
console.log(chalk.gray(` - ${s.role} (${s.tool})`));
|
|
299
|
-
});
|
|
300
|
-
console.log('');
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (pendingQuestions.length > 0) {
|
|
304
|
-
console.log(chalk.yellow(` ⚠️ 대기 질문: ${pendingQuestions.length}개`));
|
|
305
|
-
console.log('');
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
console.log(chalk.cyan('━'.repeat(60)));
|
|
309
|
-
console.log('');
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function buildSystemPrompt(workspace, role, roleContent) {
|
|
313
|
-
const artifactsDir = path.join(workspace, 'artifacts');
|
|
314
|
-
const rulesDir = path.join(workspace, 'rules');
|
|
315
|
-
|
|
316
|
-
let prompt = `# Role: ${role}\n\n`;
|
|
317
|
-
prompt += roleContent;
|
|
318
|
-
prompt += '\n\n---\n\n';
|
|
319
|
-
|
|
320
|
-
// 1. 규칙 문서 - 역할별 필수 규칙만 포함
|
|
321
|
-
const roleRules = {
|
|
322
|
-
planner: ['iteration.md', 'escalation.md', 'document-priority.md'],
|
|
323
|
-
improver: ['iteration.md', 'escalation.md', 'document-priority.md', 'rfc.md'],
|
|
324
|
-
developer: ['iteration.md', 'escalation.md', 'rollback.md', 'document-priority.md', 'rfc.md'],
|
|
325
|
-
reviewer: ['iteration.md', 'rollback.md', 'escalation.md', 'document-priority.md'],
|
|
326
|
-
documenter: ['escalation.md', 'document-priority.md'],
|
|
327
|
-
analyzer: ['escalation.md', 'document-priority.md'],
|
|
328
|
-
manager: ['escalation.md', 'document-priority.md', 'rfc.md'] // Manager는 모든 규칙 참고
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
const requiredRules = roleRules[role] || [];
|
|
332
|
-
|
|
333
|
-
prompt += '# 규칙 (Rules)\n\n';
|
|
334
|
-
prompt += `이 역할에 적용되는 필수 규칙: ${requiredRules.join(', ')}\n\n`;
|
|
335
|
-
|
|
336
|
-
if (fs.existsSync(rulesDir) && requiredRules.length > 0) {
|
|
337
|
-
requiredRules.forEach(ruleFile => {
|
|
338
|
-
const rulePath = path.join(rulesDir, ruleFile);
|
|
339
|
-
if (fs.existsSync(rulePath)) {
|
|
340
|
-
try {
|
|
341
|
-
const content = fs.readFileSync(rulePath, 'utf-8');
|
|
342
|
-
prompt += `## ${ruleFile}\n\n`;
|
|
343
|
-
prompt += content;
|
|
344
|
-
prompt += '\n\n---\n\n';
|
|
345
|
-
} catch (err) {
|
|
346
|
-
prompt += `## ${ruleFile} (읽기 실패)\n\n`;
|
|
347
|
-
}
|
|
348
|
-
} else {
|
|
349
|
-
prompt += `## ${ruleFile} (파일 없음)\n\n`;
|
|
350
|
-
}
|
|
351
|
-
});
|
|
352
|
-
} else if (requiredRules.length === 0) {
|
|
353
|
-
prompt += '(이 역할에 필수 규칙이 지정되지 않았습니다)\n\n';
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// 2. 핵심 산출물 전체 포함 (우선순위 높은 문서)
|
|
357
|
-
prompt += '# 핵심 산출물 (Core Artifacts)\n\n';
|
|
358
|
-
|
|
359
|
-
const priorityArtifacts = [
|
|
360
|
-
'decision.md', // 최우선 문서
|
|
361
|
-
'project.md', // 기술 기준 (Frozen)
|
|
362
|
-
'plan.md' // 요구사항
|
|
363
|
-
];
|
|
364
|
-
|
|
365
|
-
priorityArtifacts.forEach(artifactFile => {
|
|
366
|
-
const artifactPath = path.join(artifactsDir, artifactFile);
|
|
367
|
-
if (fs.existsSync(artifactPath)) {
|
|
368
|
-
try {
|
|
369
|
-
const content = fs.readFileSync(artifactPath, 'utf-8');
|
|
370
|
-
prompt += `## ${artifactFile}\n\n`;
|
|
371
|
-
prompt += content;
|
|
372
|
-
prompt += '\n\n---\n\n';
|
|
373
|
-
} catch (err) {
|
|
374
|
-
prompt += `## ${artifactFile} (읽기 실패)\n\n`;
|
|
375
|
-
}
|
|
376
|
-
} else {
|
|
377
|
-
prompt += `## ${artifactFile} (아직 작성되지 않음)\n\n`;
|
|
378
|
-
}
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
// 2.1 현재 활성 스프린트 포함
|
|
382
|
-
prompt += '# 현재 스프린트 정보\n\n';
|
|
383
|
-
|
|
384
|
-
const sprintsDir = path.join(artifactsDir, 'sprints');
|
|
385
|
-
if (fs.existsSync(sprintsDir)) {
|
|
386
|
-
const sprints = fs.readdirSync(sprintsDir, { withFileTypes: true })
|
|
387
|
-
.filter(dirent => dirent.isDirectory() && !dirent.name.startsWith('_'))
|
|
388
|
-
.map(dirent => dirent.name);
|
|
389
|
-
|
|
390
|
-
// 가장 최근 스프린트 찾기 (sprint-N 형식)
|
|
391
|
-
const activeSprint = sprints
|
|
392
|
-
.filter(name => /^sprint-\d+$/.test(name))
|
|
393
|
-
.sort((a, b) => {
|
|
394
|
-
const numA = parseInt(a.split('-')[1]);
|
|
395
|
-
const numB = parseInt(b.split('-')[1]);
|
|
396
|
-
return numB - numA;
|
|
397
|
-
})[0];
|
|
398
|
-
|
|
399
|
-
if (activeSprint) {
|
|
400
|
-
const sprintMetaPath = path.join(sprintsDir, activeSprint, 'meta.md');
|
|
401
|
-
if (fs.existsSync(sprintMetaPath)) {
|
|
402
|
-
try {
|
|
403
|
-
const content = fs.readFileSync(sprintMetaPath, 'utf-8');
|
|
404
|
-
prompt += `## 현재 스프린트: ${activeSprint}/meta.md\n\n`;
|
|
405
|
-
prompt += content;
|
|
406
|
-
prompt += '\n\n---\n\n';
|
|
407
|
-
} catch (err) {
|
|
408
|
-
prompt += `## ${activeSprint}/meta.md (읽기 실패)\n\n`;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// 스프린트의 Task 파일 전체 포함
|
|
413
|
-
const sprintTasksDir = path.join(sprintsDir, activeSprint, 'tasks');
|
|
414
|
-
if (fs.existsSync(sprintTasksDir)) {
|
|
415
|
-
const taskFiles = fs.readdirSync(sprintTasksDir)
|
|
416
|
-
.filter(f => f.endsWith('.md') && !f.includes('template'));
|
|
417
|
-
|
|
418
|
-
if (taskFiles.length > 0) {
|
|
419
|
-
prompt += `## 현재 스프린트 Task 파일들\n\n`;
|
|
420
|
-
|
|
421
|
-
// 각 Task 파일 내용 포함
|
|
422
|
-
taskFiles.forEach(f => {
|
|
423
|
-
const taskPath = path.join(sprintTasksDir, f);
|
|
424
|
-
try {
|
|
425
|
-
const taskContent = fs.readFileSync(taskPath, 'utf-8');
|
|
426
|
-
prompt += `### ${f}\n\n`;
|
|
427
|
-
prompt += taskContent;
|
|
428
|
-
prompt += '\n\n---\n\n';
|
|
429
|
-
} catch (err) {
|
|
430
|
-
prompt += `### ${f} (읽기 실패)\n\n`;
|
|
431
|
-
}
|
|
432
|
-
});
|
|
433
|
-
} else {
|
|
434
|
-
// Task 파일이 없는 경우
|
|
435
|
-
prompt += `## ⚠️ 스프린트에 Task 없음\n\n`;
|
|
436
|
-
prompt += `현재 스프린트(${activeSprint})에 할당된 Task가 없습니다.\n\n`;
|
|
437
|
-
prompt += '**다음 단계:**\n';
|
|
438
|
-
prompt += '1. `ada sprint add task-001 task-002` 명령으로 Task 할당\n';
|
|
439
|
-
prompt += '2. Developer 세션 재시작\n\n';
|
|
440
|
-
prompt += '---\n\n';
|
|
441
|
-
}
|
|
442
|
-
} else {
|
|
443
|
-
// tasks 디렉토리가 없는 경우
|
|
444
|
-
prompt += `## ⚠️ tasks 디렉토리 없음\n\n`;
|
|
445
|
-
prompt += `현재 스프린트(${activeSprint})에 tasks 디렉토리가 없습니다.\n\n`;
|
|
446
|
-
prompt += '스프린트 구조가 올바르지 않습니다. `ada sprint create` 명령으로 재생성하세요.\n\n';
|
|
447
|
-
prompt += '---\n\n';
|
|
448
|
-
}
|
|
449
|
-
} else {
|
|
450
|
-
// 스프린트가 없는 경우
|
|
451
|
-
prompt += '## ⚠️ 현재 활성 스프린트 없음\n\n';
|
|
452
|
-
prompt += '스프린트가 아직 생성되지 않았습니다.\n\n';
|
|
453
|
-
prompt += '**다음 단계:**\n';
|
|
454
|
-
prompt += '1. Planner가 plan.md와 backlog/ Task를 작성했는지 확인\n';
|
|
455
|
-
prompt += '2. `ada sprint create` 명령으로 스프린트 생성\n';
|
|
456
|
-
prompt += '3. `ada sprint add task-001 task-002` 명령으로 Task 할당\n';
|
|
457
|
-
prompt += '4. Developer 세션 재시작\n\n';
|
|
458
|
-
prompt += '**참고:** Developer는 스프린트가 있어야 작업할 수 있습니다.\n';
|
|
459
|
-
prompt += '스프린트 없이는 어떤 Task를 해야 할지 알 수 없습니다.\n\n';
|
|
460
|
-
prompt += '---\n\n';
|
|
461
|
-
}
|
|
462
|
-
} else {
|
|
463
|
-
// sprints 디렉토리 자체가 없는 경우
|
|
464
|
-
prompt += '## ⚠️ sprints 디렉토리 없음\n\n';
|
|
465
|
-
prompt += 'sprints 디렉토리가 존재하지 않습니다.\n\n';
|
|
466
|
-
prompt += '**다음 단계:**\n';
|
|
467
|
-
prompt += '1. `ada sprint create` 명령으로 첫 스프린트 생성\n';
|
|
468
|
-
prompt += '2. Task를 스프린트에 추가\n';
|
|
469
|
-
prompt += '3. Developer 세션 재시작\n\n';
|
|
470
|
-
prompt += '---\n\n';
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// 2.2 Backlog Task 목록
|
|
474
|
-
const backlogDir = path.join(artifactsDir, 'backlog');
|
|
475
|
-
if (fs.existsSync(backlogDir)) {
|
|
476
|
-
const backlogFiles = fs.readdirSync(backlogDir)
|
|
477
|
-
.filter(f => f.endsWith('.md') && f.startsWith('task-'));
|
|
478
|
-
|
|
479
|
-
if (backlogFiles.length > 0) {
|
|
480
|
-
prompt += `## Backlog Task 목록\n\n`;
|
|
481
|
-
prompt += `다음 Task 파일들을 필요 시 읽어서 확인하세요:\n`;
|
|
482
|
-
backlogFiles.forEach(f => {
|
|
483
|
-
prompt += `- backlog/${f}\n`;
|
|
484
|
-
});
|
|
485
|
-
prompt += '\n---\n\n';
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// 3. 인터페이스 문서 전체 포함 (api.md, ui.md 등)
|
|
490
|
-
prompt += '# 인터페이스 산출물 (Interface Artifacts)\n\n';
|
|
491
|
-
|
|
492
|
-
const interfaceArtifacts = ['api.md', 'ui.md', 'public-api.md', 'commands.md', 'output-format.md',
|
|
493
|
-
'game-systems.md', 'assets.md', 'hud.md', 'examples.md', 'changelog.md'];
|
|
494
|
-
|
|
495
|
-
let hasInterfaceDoc = false;
|
|
496
|
-
interfaceArtifacts.forEach(artifactFile => {
|
|
497
|
-
const artifactPath = path.join(artifactsDir, artifactFile);
|
|
498
|
-
if (fs.existsSync(artifactPath)) {
|
|
499
|
-
hasInterfaceDoc = true;
|
|
500
|
-
try {
|
|
501
|
-
const content = fs.readFileSync(artifactPath, 'utf-8');
|
|
502
|
-
prompt += `## ${artifactFile}\n\n`;
|
|
503
|
-
prompt += content;
|
|
504
|
-
prompt += '\n\n---\n\n';
|
|
505
|
-
} catch (err) {
|
|
506
|
-
prompt += `## ${artifactFile} (읽기 실패)\n\n`;
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
if (!hasInterfaceDoc) {
|
|
512
|
-
prompt += '(인터페이스 문서 없음)\n\n';
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
// 4. 나머지 산출물은 목록만 (필요 시 AI가 파일 읽기 도구 사용)
|
|
516
|
-
prompt += '# 기타 산출물 (목록)\n\n';
|
|
517
|
-
|
|
518
|
-
if (fs.existsSync(artifactsDir)) {
|
|
519
|
-
const allArtifacts = fs.readdirSync(artifactsDir, { withFileTypes: true });
|
|
520
|
-
const otherFiles = allArtifacts
|
|
521
|
-
.filter(dirent => dirent.isFile() && dirent.name.endsWith('.md'))
|
|
522
|
-
.map(dirent => dirent.name)
|
|
523
|
-
.filter(name => !priorityArtifacts.includes(name) && !interfaceArtifacts.includes(name));
|
|
524
|
-
|
|
525
|
-
if (otherFiles.length > 0) {
|
|
526
|
-
prompt += '다음 산출물들은 필요 시 파일을 읽어서 확인하세요:\n';
|
|
527
|
-
otherFiles.forEach(f => {
|
|
528
|
-
prompt += `- artifacts/${f}\n`;
|
|
529
|
-
});
|
|
530
|
-
prompt += '\n';
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
// features 디렉토리 확인
|
|
534
|
-
const featuresDir = path.join(artifactsDir, 'features');
|
|
535
|
-
if (fs.existsSync(featuresDir)) {
|
|
536
|
-
const features = fs.readdirSync(featuresDir, { withFileTypes: true })
|
|
537
|
-
.filter(dirent => dirent.isDirectory() && !dirent.name.startsWith('_'))
|
|
538
|
-
.map(dirent => dirent.name);
|
|
539
|
-
|
|
540
|
-
if (features.length > 0) {
|
|
541
|
-
prompt += '\n**Features 디렉토리:**\n';
|
|
542
|
-
features.forEach(feature => {
|
|
543
|
-
prompt += `- features/${feature}/\n`;
|
|
544
|
-
});
|
|
545
|
-
prompt += '\n필요한 Feature 문서는 파일을 직접 읽어서 확인하세요.\n';
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
// RFC 디렉토리 확인
|
|
550
|
-
const rfcDir = path.join(artifactsDir, 'rfc');
|
|
551
|
-
if (fs.existsSync(rfcDir)) {
|
|
552
|
-
const rfcs = fs.readdirSync(rfcDir)
|
|
553
|
-
.filter(f => f.endsWith('.md') && !f.includes('template'));
|
|
554
|
-
|
|
555
|
-
if (rfcs.length > 0) {
|
|
556
|
-
prompt += '\n**RFC 문서:**\n';
|
|
557
|
-
rfcs.forEach(rfc => {
|
|
558
|
-
prompt += `- rfc/${rfc}\n`;
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
prompt += '\n---\n\n';
|
|
565
|
-
prompt += '# 작업 지침\n\n';
|
|
566
|
-
prompt += '- **문서 기준 판단**: 위에 포함된 문서 내용을 기준으로 판단하세요.\n';
|
|
567
|
-
prompt += '- **추측 금지**: 문서에 없는 내용은 추측하지 말고 사용자에게 에스컬레이션하세요.\n';
|
|
568
|
-
prompt += '- **규칙 준수**: 모든 규칙(Rules)을 반드시 따라야 합니다.\n';
|
|
569
|
-
prompt += '- **우선순위**: 문서 간 충돌 시 document-priority.md의 우선순위를 따르세요.\n';
|
|
570
|
-
prompt += '- **현재 범위**: 현재 스프린트 meta.md에 정의된 Task만 작업하세요.\n';
|
|
571
|
-
prompt += '- **파일 읽기**: 필요한 경우 목록에 표시된 산출물을 파일 읽기 도구로 확인하세요.\n';
|
|
572
|
-
prompt += '\n';
|
|
573
|
-
prompt += '## 멀티 세션 상태 관리\n\n';
|
|
574
|
-
prompt += '여러 터미널에서 동시에 다른 역할이 작업할 수 있습니다.\n';
|
|
575
|
-
prompt += '상태 공유를 위해 `ai-dev-team/.ada-status.json` 파일을 사용하세요.\n\n';
|
|
576
|
-
prompt += '**주요 작업:**\n';
|
|
577
|
-
prompt += '1. **Task 진행 상황 업데이트**: 작업 시작/완료 시 taskProgress 업데이트\n';
|
|
578
|
-
prompt += '2. **질문 등록**: 사용자에게 질문이 필요하면 pendingQuestions에 추가\n';
|
|
579
|
-
prompt += '3. **알림 전송**: 다른 역할에게 알릴 사항이 있으면 notifications 추가\n';
|
|
580
|
-
prompt += '4. **상태 파일**: .ada-status.json을 통해 세션 간 상태 공유\n';
|
|
581
|
-
|
|
582
|
-
return prompt;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
async function launchTool(tool, systemPrompt, promptFile, logMessage, options = {}) {
|
|
586
|
-
// 프롬프트 파일의 상대 경로 (작업 디렉토리 기준)
|
|
587
|
-
const relativePromptPath = path.relative(process.cwd(), promptFile);
|
|
588
|
-
|
|
589
|
-
// 도구별 설정
|
|
590
|
-
const commands = {
|
|
591
|
-
claude: {
|
|
592
|
-
cmd: 'claude',
|
|
593
|
-
args: ['--system-prompt-file', promptFile],
|
|
594
|
-
automation: 'perfect'
|
|
595
|
-
},
|
|
596
|
-
gemini: {
|
|
597
|
-
cmd: 'gemini',
|
|
598
|
-
args: [],
|
|
599
|
-
env: {
|
|
600
|
-
GEMINI_SYSTEM_MD: promptFile // 시스템 프롬프트 파일 경로
|
|
601
|
-
},
|
|
602
|
-
automation: 'perfect'
|
|
603
|
-
},
|
|
604
|
-
codex: {
|
|
605
|
-
cmd: 'codex',
|
|
606
|
-
args: [],
|
|
607
|
-
automation: 'manual',
|
|
608
|
-
instruction: `@${relativePromptPath}`
|
|
609
|
-
},
|
|
610
|
-
copilot: {
|
|
611
|
-
cmd: 'gh',
|
|
612
|
-
args: ['copilot'],
|
|
613
|
-
automation: 'manual',
|
|
614
|
-
instruction: `@${relativePromptPath}`
|
|
615
|
-
}
|
|
616
|
-
};
|
|
617
|
-
|
|
618
|
-
const config = commands[tool];
|
|
619
|
-
let { cmd, args } = config;
|
|
620
|
-
let usePromptStdin = false;
|
|
621
|
-
let promptInput = null;
|
|
622
|
-
|
|
623
|
-
if (options.captureOutput) {
|
|
624
|
-
// captureOutput 모드일 때 Claude는 --print 옵션 필요
|
|
625
|
-
if (tool === 'claude') {
|
|
626
|
-
args = ['--print', '-p', '위 시스템 프롬프트의 지시에 따라 JSON으로 응답하세요.', '--system-prompt-file', promptFile];
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
// codex exec는 프롬프트를 stdin으로 받는다
|
|
630
|
-
if (tool === 'codex') {
|
|
631
|
-
args = ['exec', '-'];
|
|
632
|
-
usePromptStdin = true;
|
|
633
|
-
promptInput = systemPrompt;
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
// gemini는 Stdin으로 프롬프트 전달 (에러 해결: No input provided via stdin)
|
|
637
|
-
if (tool === 'gemini') {
|
|
638
|
-
const userPrompt = '위 시스템 프롬프트의 지시에 따라 JSON으로 응답하세요.';
|
|
639
|
-
args = ['-o', 'text'];
|
|
640
|
-
usePromptStdin = true;
|
|
641
|
-
promptInput = userPrompt;
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
// 도구 존재 확인 (Windows: where, Unix: which)
|
|
646
|
-
const whichCmd = process.platform === 'win32' ? 'where' : 'which';
|
|
647
|
-
const which = spawn(whichCmd, [cmd], { shell: true });
|
|
648
|
-
|
|
649
|
-
return new Promise((resolve, reject) => {
|
|
650
|
-
which.on('close', (code) => {
|
|
651
|
-
if (code !== 0) {
|
|
652
|
-
console.log(chalk.yellow(`⚠️ ${tool} CLI가 설치되어 있지 않습니다.`));
|
|
653
|
-
logMessage('WARN', `${tool} CLI not found, prompt displayed`);
|
|
654
|
-
resolve(null);
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
if (!options.captureOutput) {
|
|
659
|
-
console.log('');
|
|
660
|
-
if (config.automation === 'perfect') {
|
|
661
|
-
console.log(chalk.green('━'.repeat(60)));
|
|
662
|
-
console.log(chalk.green.bold('✓ 역할이 자동으로 설정됩니다'));
|
|
663
|
-
console.log(chalk.green('━'.repeat(60)));
|
|
664
|
-
console.log('');
|
|
665
|
-
console.log(chalk.gray(`시스템 프롬프트: ${relativePromptPath}`));
|
|
666
|
-
console.log('');
|
|
667
|
-
} else if (config.automation === 'manual') {
|
|
668
|
-
console.log(chalk.yellow('━'.repeat(60)));
|
|
669
|
-
console.log(chalk.yellow.bold('⚠️ 수동 설정이 필요합니다'));
|
|
670
|
-
console.log(chalk.yellow('━'.repeat(60)));
|
|
671
|
-
console.log('');
|
|
672
|
-
console.log('CLI가 실행되면 아래 명령어를 복사해서 입력하세요:');
|
|
673
|
-
console.log('');
|
|
674
|
-
console.log(chalk.bgWhite.black.bold(` ${config.instruction} `));
|
|
675
|
-
console.log('');
|
|
676
|
-
}
|
|
677
|
-
console.log(chalk.green(`✓ ${tool} 실행 중...`));
|
|
678
|
-
console.log('');
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
logMessage('INFO', `${tool} CLI 실행 (automation: ${config.automation}, captureOutput: ${!!options.captureOutput})`);
|
|
682
|
-
|
|
683
|
-
// 환경 변수 병합
|
|
684
|
-
const envVars = {
|
|
685
|
-
...process.env,
|
|
686
|
-
ADA_SYSTEM_PROMPT: systemPrompt,
|
|
687
|
-
...(config.env || {})
|
|
688
|
-
};
|
|
689
|
-
|
|
690
|
-
// 캡처 모드에 따라 stdio 설정 변경
|
|
691
|
-
const stdioConfig = options.captureOutput
|
|
692
|
-
? (usePromptStdin ? ['pipe', 'pipe', 'pipe'] : ['ignore', 'pipe', 'pipe'])
|
|
693
|
-
: 'inherit';
|
|
694
|
-
|
|
695
|
-
const child = spawn(cmd, args, {
|
|
696
|
-
stdio: stdioConfig,
|
|
697
|
-
shell: true,
|
|
698
|
-
env: envVars
|
|
699
|
-
});
|
|
700
|
-
|
|
701
|
-
if (typeof options.onSpawn === 'function') {
|
|
702
|
-
options.onSpawn(child);
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
let capturedOutput = '';
|
|
706
|
-
let capturedError = '';
|
|
707
|
-
|
|
708
|
-
if (options.captureOutput) {
|
|
709
|
-
child.stdout.on('data', (data) => {
|
|
710
|
-
capturedOutput += data.toString();
|
|
711
|
-
});
|
|
712
|
-
child.stderr.on('data', (data) => {
|
|
713
|
-
capturedError += data.toString();
|
|
714
|
-
});
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
if (options.captureOutput && usePromptStdin && child.stdin) {
|
|
718
|
-
const input = promptInput || systemPrompt;
|
|
719
|
-
child.stdin.write(input);
|
|
720
|
-
child.stdin.end();
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
child.on('close', (code) => {
|
|
724
|
-
if (code === 0) {
|
|
725
|
-
resolve(options.captureOutput ? capturedOutput : null);
|
|
726
|
-
} else {
|
|
727
|
-
const errorMsg = options.captureOutput
|
|
728
|
-
? `${tool} exited with code ${code}. Stderr: ${capturedError}`
|
|
729
|
-
: `${tool} exited with code ${code}`;
|
|
730
|
-
reject(new Error(errorMsg));
|
|
731
|
-
}
|
|
732
|
-
});
|
|
733
|
-
|
|
734
|
-
child.on('error', (err) => {
|
|
735
|
-
reject(err);
|
|
736
|
-
});
|
|
737
|
-
});
|
|
738
|
-
});
|
|
739
|
-
}
|