@silbaram/artifact-driven-agent 0.1.6 → 0.1.9
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 +709 -516
- package/ai-dev-team/.ada-status.json +10 -0
- package/ai-dev-team/.ada-version +6 -0
- package/ai-dev-team/.current-template +1 -0
- package/ai-dev-team/.sessions/logs/20260124-014551-00f04724.log +5 -0
- package/ai-dev-team/.sessions/logs/20260124-014623-cb2b1d44.log +5 -0
- package/ai-dev-team/ada.config.json +15 -0
- package/ai-dev-team/artifacts/api.md +212 -0
- package/ai-dev-team/artifacts/decision.md +72 -0
- package/ai-dev-team/artifacts/improvement-reports/IMP-0000-template.md +57 -0
- package/ai-dev-team/artifacts/plan.md +187 -0
- package/ai-dev-team/artifacts/project.md +193 -0
- package/ai-dev-team/artifacts/sprints/_template/docs/release-notes.md +37 -0
- package/ai-dev-team/artifacts/sprints/_template/meta.md +54 -0
- package/ai-dev-team/artifacts/sprints/_template/retrospective.md +50 -0
- package/ai-dev-team/artifacts/sprints/_template/review-reports/review-template.md +49 -0
- package/ai-dev-team/artifacts/sprints/_template/tasks/task-template.md +43 -0
- package/ai-dev-team/artifacts/ui.md +104 -0
- package/ai-dev-team/roles/analyzer.md +265 -0
- package/ai-dev-team/roles/developer.md +222 -0
- package/ai-dev-team/roles/documenter.md +715 -0
- package/ai-dev-team/roles/improver.md +461 -0
- package/ai-dev-team/roles/manager.md +544 -0
- package/ai-dev-team/roles/planner.md +398 -0
- package/ai-dev-team/roles/reviewer.md +294 -0
- package/ai-dev-team/rules/api-change.md +198 -0
- package/ai-dev-team/rules/document-priority.md +199 -0
- package/ai-dev-team/rules/escalation.md +172 -0
- package/ai-dev-team/rules/iteration.md +236 -0
- package/ai-dev-team/rules/rfc.md +31 -0
- package/ai-dev-team/rules/rollback.md +218 -0
- package/bin/cli.js +49 -5
- package/core/artifacts/sprints/_template/meta.md +4 -4
- package/core/docs-templates/mkdocs/docs/architecture/overview.md +29 -0
- package/core/docs-templates/mkdocs/docs/changelog.md +36 -0
- package/core/docs-templates/mkdocs/docs/contributing/contributing.md +60 -0
- package/core/docs-templates/mkdocs/docs/getting-started/configuration.md +51 -0
- package/core/docs-templates/mkdocs/docs/getting-started/installation.md +41 -0
- package/core/docs-templates/mkdocs/docs/getting-started/quick-start.md +56 -0
- package/core/docs-templates/mkdocs/docs/guides/api-reference.md +83 -0
- package/core/docs-templates/mkdocs/docs/index.md +32 -0
- package/core/docs-templates/mkdocs/mkdocs.yml +86 -0
- package/core/roles/analyzer.md +32 -10
- package/core/roles/developer.md +222 -223
- package/core/roles/documenter.md +592 -170
- package/core/roles/improver.md +461 -0
- package/core/roles/manager.md +4 -1
- package/core/roles/planner.md +160 -10
- package/core/roles/reviewer.md +31 -3
- package/core/rules/document-priority.md +2 -1
- package/core/rules/rollback.md +3 -3
- package/package.json +1 -1
- package/src/commands/config.js +371 -0
- package/src/commands/docs.js +502 -0
- package/src/commands/interactive.js +324 -33
- package/src/commands/monitor.js +236 -0
- package/src/commands/run.js +360 -122
- package/src/commands/sessions.js +270 -70
- package/src/commands/setup.js +22 -1
- package/src/commands/sprint.js +295 -54
- package/src/commands/status.js +34 -1
- package/src/commands/upgrade.js +416 -0
- package/src/commands/validate.js +4 -3
- package/src/index.js +1 -0
- package/src/ui/dashboard.js +518 -0
- package/src/ui/keyHandler.js +147 -0
- package/src/ui/quickActions.js +111 -0
- package/src/utils/config.js +74 -0
- package/src/utils/files.js +70 -3
- package/src/utils/sessionState.js +472 -328
- package/src/utils/sessionState.process.test.js +101 -0
- package/src/utils/sessionState.test.js +183 -0
- package/src/utils/sprintUtils.js +134 -0
- package/src/utils/taskParser.js +134 -0
- package/src/utils/taskParser.test.js +76 -0
- package/ai-dev-team/artifacts/features/_template/qa.md +0 -16
- package/examples/todo-app/README.md +0 -23
- package/examples/todo-app/artifacts/backlog.md +0 -23
- package/examples/todo-app/artifacts/plan.md +0 -23
- package/examples/todo-app/artifacts/project.md +0 -23
package/src/commands/run.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from 'fs-extra';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { spawn } from 'child_process';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
|
+
import inquirer from 'inquirer';
|
|
5
6
|
import {
|
|
6
7
|
getWorkspaceDir,
|
|
7
8
|
getSessionsDir,
|
|
@@ -15,33 +16,31 @@ import {
|
|
|
15
16
|
import {
|
|
16
17
|
registerSession,
|
|
17
18
|
unregisterSession,
|
|
18
|
-
|
|
19
|
+
updateSessionDetails,
|
|
19
20
|
getActiveSessions,
|
|
20
21
|
getPendingQuestions
|
|
21
22
|
} from '../utils/sessionState.js';
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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 = {}) {
|
|
30
33
|
const roles = getAvailableRoles();
|
|
31
34
|
const tools = ['claude', 'codex', 'gemini', 'copilot'];
|
|
32
35
|
|
|
33
36
|
// 역할 검증
|
|
34
37
|
if (!roles.includes(role)) {
|
|
35
|
-
|
|
36
|
-
console.log(chalk.gray(`사용 가능: ${roles.join(', ')}`));
|
|
37
|
-
process.exit(1);
|
|
38
|
+
throw new Error(`알 수 없는 역할입니다: ${role} (사용 가능: ${roles.join(', ')})`);
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
// 도구 검증
|
|
41
42
|
if (!tools.includes(tool)) {
|
|
42
|
-
|
|
43
|
-
console.log(chalk.gray(`사용 가능: ${tools.join(', ')}`));
|
|
44
|
-
process.exit(1);
|
|
43
|
+
throw new Error(`알 수 없는 도구입니다: ${tool} (사용 가능: ${tools.join(', ')})`);
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
const workspace = getWorkspaceDir();
|
|
@@ -55,7 +54,7 @@ export async function run(role, tool) {
|
|
|
55
54
|
fs.ensureDirSync(sessionDir);
|
|
56
55
|
fs.ensureDirSync(logsDir);
|
|
57
56
|
|
|
58
|
-
// 세션 정보
|
|
57
|
+
// 세션 정보 객체
|
|
59
58
|
const sessionInfo = {
|
|
60
59
|
session_id: sessionId,
|
|
61
60
|
role: role,
|
|
@@ -64,38 +63,199 @@ export async function run(role, tool) {
|
|
|
64
63
|
started_at: getTimestamp(),
|
|
65
64
|
status: 'active'
|
|
66
65
|
};
|
|
67
|
-
|
|
66
|
+
const sessionFile = path.join(sessionDir, 'session.json');
|
|
67
|
+
fs.writeFileSync(sessionFile, JSON.stringify(sessionInfo, null, 2));
|
|
68
68
|
|
|
69
|
-
// 로그
|
|
69
|
+
// 로그 헬퍼
|
|
70
70
|
const logFile = path.join(logsDir, `${sessionId}.log`);
|
|
71
71
|
const logMessage = (level, msg) => {
|
|
72
72
|
const line = `[${getTimestamp()}] [${level}] ${msg}\n`;
|
|
73
73
|
fs.appendFileSync(logFile, line);
|
|
74
|
+
// 옵션에 따라 콘솔 출력 제어 가능 (현재는 항상 출력)
|
|
74
75
|
};
|
|
75
76
|
|
|
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
|
+
};
|
|
77
97
|
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
98
|
+
// 시그널 핸들러 (Ctrl+C 등 강제 종료 시 세션 정리)
|
|
99
|
+
const handleSignal = (signal) => {
|
|
100
|
+
logMessage('INFO', `시그널 수신: ${signal}`);
|
|
101
|
+
cleanupSession('completed', `사용자 종료 (${signal})`);
|
|
81
102
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
103
|
+
// 핸들러 제거
|
|
104
|
+
process.removeListener('SIGINT', handleSignal);
|
|
105
|
+
process.removeListener('SIGTERM', handleSignal);
|
|
85
106
|
|
|
86
|
-
|
|
87
|
-
|
|
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
|
+
});
|
|
88
161
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
logMessage('INFO', `시스템 프롬프트 저장: ${promptFile}`);
|
|
162
|
+
// 시그널 핸들러 제거
|
|
163
|
+
process.removeListener('SIGINT', handleSignal);
|
|
164
|
+
process.removeListener('SIGTERM', handleSignal);
|
|
93
165
|
|
|
94
|
-
|
|
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
|
+
// 다른 활성 세션 정보
|
|
95
255
|
const activeSessions = getActiveSessions().filter(s => s.sessionId !== sessionId);
|
|
96
256
|
const pendingQuestions = getPendingQuestions();
|
|
97
257
|
|
|
98
|
-
// 터미널 타이틀
|
|
258
|
+
// 터미널 타이틀
|
|
99
259
|
const terminalTitle = `ADA: ${role} (${tool})`;
|
|
100
260
|
process.stdout.write(`\x1b]0;${terminalTitle}\x07`);
|
|
101
261
|
|
|
@@ -105,16 +265,15 @@ export async function run(role, tool) {
|
|
|
105
265
|
console.log(chalk.cyan('━'.repeat(60)));
|
|
106
266
|
console.log('');
|
|
107
267
|
|
|
108
|
-
// 역할 강조 표시
|
|
109
268
|
const roleEmojis = {
|
|
110
269
|
'analyzer': '🔍',
|
|
111
270
|
'planner': '📋',
|
|
271
|
+
'improver': '🔧',
|
|
112
272
|
'architect': '🏛️',
|
|
113
273
|
'developer': '💻',
|
|
114
274
|
'backend': '⚙️',
|
|
115
275
|
'frontend': '🎨',
|
|
116
276
|
'reviewer': '👀',
|
|
117
|
-
'qa': '🧪',
|
|
118
277
|
'manager': '👔',
|
|
119
278
|
'library-developer': '📚',
|
|
120
279
|
'game-logic': '🎮',
|
|
@@ -130,10 +289,9 @@ export async function run(role, tool) {
|
|
|
130
289
|
console.log(chalk.white(` 템플릿: ${chalk.green(template)}`));
|
|
131
290
|
console.log(chalk.white(` 도구: ${chalk.green(tool)}`));
|
|
132
291
|
console.log(chalk.white(` 작업공간: ${chalk.gray('ai-dev-team/')}`));
|
|
133
|
-
console.log(chalk.white(` 로그: ${chalk.gray(
|
|
292
|
+
console.log(chalk.white(` 로그: ${chalk.gray('.sessions/logs/' + sessionId + '.log')}`));
|
|
134
293
|
console.log('');
|
|
135
294
|
|
|
136
|
-
// 멀티 세션 정보 표시
|
|
137
295
|
if (activeSessions.length > 0) {
|
|
138
296
|
console.log(chalk.white(` 🔗 활성 세션: ${chalk.yellow(activeSessions.length)}개`));
|
|
139
297
|
activeSessions.forEach(s => {
|
|
@@ -149,32 +307,6 @@ export async function run(role, tool) {
|
|
|
149
307
|
|
|
150
308
|
console.log(chalk.cyan('━'.repeat(60)));
|
|
151
309
|
console.log('');
|
|
152
|
-
|
|
153
|
-
// 도구별 실행
|
|
154
|
-
try {
|
|
155
|
-
await launchTool(tool, systemPrompt, promptFile, logMessage);
|
|
156
|
-
|
|
157
|
-
// 세션 완료 처리
|
|
158
|
-
sessionInfo.status = 'completed';
|
|
159
|
-
sessionInfo.ended_at = getTimestamp();
|
|
160
|
-
fs.writeFileSync(path.join(sessionDir, 'session.json'), JSON.stringify(sessionInfo, null, 2));
|
|
161
|
-
logMessage('INFO', '세션 종료');
|
|
162
|
-
|
|
163
|
-
// 멀티 세션: 상태 파일에서 세션 제거
|
|
164
|
-
unregisterSession(sessionId);
|
|
165
|
-
logMessage('INFO', `세션 해제: ${sessionId}`);
|
|
166
|
-
} catch (error) {
|
|
167
|
-
sessionInfo.status = 'error';
|
|
168
|
-
sessionInfo.error = error.message;
|
|
169
|
-
fs.writeFileSync(path.join(sessionDir, 'session.json'), JSON.stringify(sessionInfo, null, 2));
|
|
170
|
-
logMessage('ERROR', error.message);
|
|
171
|
-
|
|
172
|
-
// 멀티 세션: 에러 시에도 세션 제거
|
|
173
|
-
unregisterSession(sessionId);
|
|
174
|
-
logMessage('INFO', `세션 해제 (에러): ${sessionId}`);
|
|
175
|
-
|
|
176
|
-
throw error;
|
|
177
|
-
}
|
|
178
310
|
}
|
|
179
311
|
|
|
180
312
|
function buildSystemPrompt(workspace, role, roleContent) {
|
|
@@ -185,21 +317,40 @@ function buildSystemPrompt(workspace, role, roleContent) {
|
|
|
185
317
|
prompt += roleContent;
|
|
186
318
|
prompt += '\n\n---\n\n';
|
|
187
319
|
|
|
188
|
-
// 1. 규칙 문서
|
|
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
|
+
|
|
189
333
|
prompt += '# 규칙 (Rules)\n\n';
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
334
|
+
prompt += `이 역할에 적용되는 필수 규칙: ${requiredRules.join(', ')}\n\n`;
|
|
335
|
+
|
|
336
|
+
if (fs.existsSync(rulesDir) && requiredRules.length > 0) {
|
|
337
|
+
requiredRules.forEach(ruleFile => {
|
|
193
338
|
const rulePath = path.join(rulesDir, ruleFile);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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`;
|
|
201
350
|
}
|
|
202
351
|
});
|
|
352
|
+
} else if (requiredRules.length === 0) {
|
|
353
|
+
prompt += '(이 역할에 필수 규칙이 지정되지 않았습니다)\n\n';
|
|
203
354
|
}
|
|
204
355
|
|
|
205
356
|
// 2. 핵심 산출물 전체 포함 (우선순위 높은 문서)
|
|
@@ -228,6 +379,8 @@ function buildSystemPrompt(workspace, role, roleContent) {
|
|
|
228
379
|
});
|
|
229
380
|
|
|
230
381
|
// 2.1 현재 활성 스프린트 포함
|
|
382
|
+
prompt += '# 현재 스프린트 정보\n\n';
|
|
383
|
+
|
|
231
384
|
const sprintsDir = path.join(artifactsDir, 'sprints');
|
|
232
385
|
if (fs.existsSync(sprintsDir)) {
|
|
233
386
|
const sprints = fs.readdirSync(sprintsDir, { withFileTypes: true })
|
|
@@ -256,22 +409,65 @@ function buildSystemPrompt(workspace, role, roleContent) {
|
|
|
256
409
|
}
|
|
257
410
|
}
|
|
258
411
|
|
|
259
|
-
// 스프린트의 Task 파일
|
|
412
|
+
// 스프린트의 Task 파일 전체 포함
|
|
260
413
|
const sprintTasksDir = path.join(sprintsDir, activeSprint, 'tasks');
|
|
261
414
|
if (fs.existsSync(sprintTasksDir)) {
|
|
262
415
|
const taskFiles = fs.readdirSync(sprintTasksDir)
|
|
263
416
|
.filter(f => f.endsWith('.md') && !f.includes('template'));
|
|
264
417
|
|
|
265
418
|
if (taskFiles.length > 0) {
|
|
266
|
-
prompt += `## 현재 스프린트 Task
|
|
267
|
-
|
|
419
|
+
prompt += `## 현재 스프린트 Task 파일들\n\n`;
|
|
420
|
+
|
|
421
|
+
// 각 Task 파일 내용 포함
|
|
268
422
|
taskFiles.forEach(f => {
|
|
269
|
-
|
|
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
|
+
}
|
|
270
432
|
});
|
|
271
|
-
|
|
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';
|
|
272
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';
|
|
273
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';
|
|
274
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';
|
|
275
471
|
}
|
|
276
472
|
|
|
277
473
|
// 2.2 Backlog Task 목록
|
|
@@ -386,7 +582,7 @@ function buildSystemPrompt(workspace, role, roleContent) {
|
|
|
386
582
|
return prompt;
|
|
387
583
|
}
|
|
388
584
|
|
|
389
|
-
async function launchTool(tool, systemPrompt, promptFile, logMessage) {
|
|
585
|
+
async function launchTool(tool, systemPrompt, promptFile, logMessage, options = {}) {
|
|
390
586
|
// 프롬프트 파일의 상대 경로 (작업 디렉토리 기준)
|
|
391
587
|
const relativePromptPath = path.relative(process.cwd(), promptFile);
|
|
392
588
|
|
|
@@ -420,76 +616,118 @@ async function launchTool(tool, systemPrompt, promptFile, logMessage) {
|
|
|
420
616
|
};
|
|
421
617
|
|
|
422
618
|
const config = commands[tool];
|
|
423
|
-
|
|
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
|
+
}
|
|
424
628
|
|
|
425
|
-
|
|
426
|
-
|
|
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 });
|
|
427
648
|
|
|
428
649
|
return new Promise((resolve, reject) => {
|
|
429
650
|
which.on('close', (code) => {
|
|
430
651
|
if (code !== 0) {
|
|
431
652
|
console.log(chalk.yellow(`⚠️ ${tool} CLI가 설치되어 있지 않습니다.`));
|
|
432
|
-
console.log('');
|
|
433
|
-
console.log(chalk.white('시스템 프롬프트가 다음 파일에 저장되었습니다:'));
|
|
434
|
-
console.log(chalk.cyan(` ${relativePromptPath}`));
|
|
435
|
-
console.log('');
|
|
436
|
-
console.log(chalk.gray('─'.repeat(60)));
|
|
437
|
-
console.log(systemPrompt);
|
|
438
|
-
console.log(chalk.gray('─'.repeat(60)));
|
|
439
|
-
console.log('');
|
|
440
|
-
console.log(chalk.gray('위 내용을 복사하여 AI 도구에 붙여넣거나, 파일을 읽도록 하세요.'));
|
|
441
653
|
logMessage('WARN', `${tool} CLI not found, prompt displayed`);
|
|
442
|
-
resolve();
|
|
654
|
+
resolve(null);
|
|
443
655
|
return;
|
|
444
656
|
}
|
|
445
657
|
|
|
446
|
-
|
|
447
|
-
console.log('');
|
|
448
|
-
if (config.automation === 'perfect') {
|
|
449
|
-
// 완전 자동화: 간단한 성공 메시지
|
|
450
|
-
console.log(chalk.green('━'.repeat(60)));
|
|
451
|
-
console.log(chalk.green.bold('✓ 역할이 자동으로 설정됩니다'));
|
|
452
|
-
console.log(chalk.green('━'.repeat(60)));
|
|
453
|
-
console.log('');
|
|
454
|
-
console.log(chalk.gray(`시스템 프롬프트: ${relativePromptPath}`));
|
|
658
|
+
if (!options.captureOutput) {
|
|
455
659
|
console.log('');
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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} 실행 중...`));
|
|
467
678
|
console.log('');
|
|
468
679
|
}
|
|
469
680
|
|
|
470
|
-
|
|
471
|
-
console.log(chalk.green(`✓ ${tool} 실행 중...`));
|
|
472
|
-
console.log('');
|
|
473
|
-
logMessage('INFO', `${tool} CLI 실행 (automation: ${config.automation})`);
|
|
681
|
+
logMessage('INFO', `${tool} CLI 실행 (automation: ${config.automation}, captureOutput: ${!!options.captureOutput})`);
|
|
474
682
|
|
|
475
|
-
// 환경 변수 병합
|
|
683
|
+
// 환경 변수 병합
|
|
476
684
|
const envVars = {
|
|
477
685
|
...process.env,
|
|
478
686
|
ADA_SYSTEM_PROMPT: systemPrompt,
|
|
479
|
-
...(config.env || {})
|
|
687
|
+
...(config.env || {})
|
|
480
688
|
};
|
|
481
689
|
|
|
690
|
+
// 캡처 모드에 따라 stdio 설정 변경
|
|
691
|
+
const stdioConfig = options.captureOutput
|
|
692
|
+
? (usePromptStdin ? ['pipe', 'pipe', 'pipe'] : ['ignore', 'pipe', 'pipe'])
|
|
693
|
+
: 'inherit';
|
|
694
|
+
|
|
482
695
|
const child = spawn(cmd, args, {
|
|
483
|
-
stdio:
|
|
696
|
+
stdio: stdioConfig,
|
|
484
697
|
shell: true,
|
|
485
698
|
env: envVars
|
|
486
699
|
});
|
|
487
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
|
+
|
|
488
723
|
child.on('close', (code) => {
|
|
489
724
|
if (code === 0) {
|
|
490
|
-
resolve();
|
|
725
|
+
resolve(options.captureOutput ? capturedOutput : null);
|
|
491
726
|
} else {
|
|
492
|
-
|
|
727
|
+
const errorMsg = options.captureOutput
|
|
728
|
+
? `${tool} exited with code ${code}. Stderr: ${capturedError}`
|
|
729
|
+
: `${tool} exited with code ${code}`;
|
|
730
|
+
reject(new Error(errorMsg));
|
|
493
731
|
}
|
|
494
732
|
});
|
|
495
733
|
|