aiag-cli 1.8.0 → 2.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.
- package/README.md +125 -89
- package/dist/api/client.d.ts +170 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +513 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/endpoints.d.ts +112 -0
- package/dist/api/endpoints.d.ts.map +1 -0
- package/dist/api/endpoints.js +150 -0
- package/dist/api/endpoints.js.map +1 -0
- package/dist/api/types.d.ts +395 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +7 -0
- package/dist/api/types.js.map +1 -0
- package/dist/auth/credentials.d.ts +73 -0
- package/dist/auth/credentials.d.ts.map +1 -0
- package/dist/auth/credentials.js +150 -0
- package/dist/auth/credentials.js.map +1 -0
- package/dist/auth/device.d.ts +58 -0
- package/dist/auth/device.d.ts.map +1 -0
- package/dist/auth/device.js +235 -0
- package/dist/auth/device.js.map +1 -0
- package/dist/auth/token.d.ts +55 -0
- package/dist/auth/token.d.ts.map +1 -0
- package/dist/auth/token.js +153 -0
- package/dist/auth/token.js.map +1 -0
- package/dist/cli.js +51 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/auto.d.ts.map +1 -1
- package/dist/commands/auto.js +321 -236
- package/dist/commands/auto.js.map +1 -1
- package/dist/commands/complete.d.ts +1 -0
- package/dist/commands/complete.d.ts.map +1 -1
- package/dist/commands/complete.js +30 -0
- package/dist/commands/complete.js.map +1 -1
- package/dist/commands/connect.d.ts +25 -0
- package/dist/commands/connect.d.ts.map +1 -0
- package/dist/commands/connect.js +305 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/login.d.ts +19 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +119 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +15 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +50 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/next.d.ts +1 -0
- package/dist/commands/next.d.ts.map +1 -1
- package/dist/commands/next.js +41 -2
- package/dist/commands/next.js.map +1 -1
- package/dist/commands/project.d.ts +13 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +92 -0
- package/dist/commands/project.js.map +1 -0
- package/dist/commands/session.d.ts +10 -2
- package/dist/commands/session.d.ts.map +1 -1
- package/dist/commands/session.js +80 -3
- package/dist/commands/session.js.map +1 -1
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +51 -0
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/sync.d.ts +33 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +555 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/work.d.ts +1 -0
- package/dist/commands/work.d.ts.map +1 -1
- package/dist/commands/work.js +22 -1
- package/dist/commands/work.js.map +1 -1
- package/dist/config/global.d.ts +54 -0
- package/dist/config/global.d.ts.map +1 -0
- package/dist/config/global.js +110 -0
- package/dist/config/global.js.map +1 -0
- package/dist/prompts/coding.d.ts +31 -0
- package/dist/prompts/coding.d.ts.map +1 -0
- package/dist/prompts/coding.js +228 -0
- package/dist/prompts/coding.js.map +1 -0
- package/dist/prompts/index.d.ts +10 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +10 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/initializer.d.ts +20 -0
- package/dist/prompts/initializer.d.ts.map +1 -0
- package/dist/prompts/initializer.js +147 -0
- package/dist/prompts/initializer.js.map +1 -0
- package/dist/sdk/client.d.ts +67 -0
- package/dist/sdk/client.d.ts.map +1 -0
- package/dist/sdk/client.js +196 -0
- package/dist/sdk/client.js.map +1 -0
- package/dist/sdk/index.d.ts +8 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +8 -0
- package/dist/sdk/index.js.map +1 -0
- package/dist/sdk/security.d.ts +43 -0
- package/dist/sdk/security.d.ts.map +1 -0
- package/dist/sdk/security.js +214 -0
- package/dist/sdk/security.js.map +1 -0
- package/dist/types.d.ts +64 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/connection.d.ts +126 -0
- package/dist/utils/connection.d.ts.map +1 -0
- package/dist/utils/connection.js +226 -0
- package/dist/utils/connection.js.map +1 -0
- package/dist/utils/messages.d.ts +76 -0
- package/dist/utils/messages.d.ts.map +1 -1
- package/dist/utils/messages.js +87 -1
- package/dist/utils/messages.js.map +1 -1
- package/dist/utils/output.d.ts +10 -0
- package/dist/utils/output.d.ts.map +1 -1
- package/dist/utils/output.js +31 -0
- package/dist/utils/output.js.map +1 -1
- package/dist/utils/prompt.d.ts +10 -0
- package/dist/utils/prompt.d.ts.map +1 -0
- package/dist/utils/prompt.js +22 -0
- package/dist/utils/prompt.js.map +1 -0
- package/dist/utils/sseClient.d.ts +183 -0
- package/dist/utils/sseClient.d.ts.map +1 -0
- package/dist/utils/sseClient.js +391 -0
- package/dist/utils/sseClient.js.map +1 -0
- package/package.json +3 -2
package/dist/commands/auto.js
CHANGED
|
@@ -3,9 +3,31 @@ import * as fs from 'fs';
|
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import { readFeatureList, writeFeatureList, getProgress } from '../utils/featureList.js';
|
|
5
5
|
import { appendToProgress, recordWorkCompleted, updateProgressSummary, updateNextSteps, } from '../utils/progress.js';
|
|
6
|
-
import { printHeader, printError, printSuccess, printWarning, printSection, colors, icons, } from '../utils/output.js';
|
|
6
|
+
import { printHeader, printError, printSuccess, printWarning, printSection, printInfo, colors, icons, } from '../utils/output.js';
|
|
7
7
|
import { getInterruptedWork, updateSessionContext as updateSessionContextUtil, clearSessionContext as clearSessionContextUtil, } from '../utils/sessionContext.js';
|
|
8
|
+
import { loadConnection, saveCurrentSession, clearCurrentSession, } from '../utils/connection.js';
|
|
9
|
+
import { getApiClient } from '../api/client.js';
|
|
8
10
|
import { PRIORITY_ORDER } from '../types.js';
|
|
11
|
+
import { getInitializerPrompt, getFeatureImplementationPrompt, } from '../prompts/index.js';
|
|
12
|
+
// Claude Agent SDK
|
|
13
|
+
import { runAgent, runFeatureImplementation, runInitializer, SECURITY_PRESET_DEVELOPMENT, } from '../sdk/index.js';
|
|
14
|
+
/**
|
|
15
|
+
* 서버에 Feature 로그 전송 (비동기, 실패해도 계속 진행)
|
|
16
|
+
*/
|
|
17
|
+
async function sendFeatureLog(serverCtx, featureId, type, message, metadata) {
|
|
18
|
+
if (!serverCtx.connected || !serverCtx.client)
|
|
19
|
+
return;
|
|
20
|
+
try {
|
|
21
|
+
await serverCtx.client.sendLog(featureId, {
|
|
22
|
+
type,
|
|
23
|
+
message,
|
|
24
|
+
metadata,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// 로그 전송 실패해도 무시 (로컬 작업 계속 진행)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
9
31
|
/**
|
|
10
32
|
* 알려진 실패 패턴 (Common failure patterns)
|
|
11
33
|
*/
|
|
@@ -76,7 +98,7 @@ export async function auto(count = '5', options = {}) {
|
|
|
76
98
|
const isVerbose = options.verbose === true;
|
|
77
99
|
const maxFeatures = isLoopMode ? Infinity : (parseInt(count, 10) || 5);
|
|
78
100
|
const cooldownMs = (options.cooldown || 5) * 1000;
|
|
79
|
-
const claudeTimeout = (options.timeout ||
|
|
101
|
+
const claudeTimeout = (options.timeout || 30) * 60 * 1000; // 기본 30분 (복잡한 기능 구현을 위해 증가)
|
|
80
102
|
const maxAttempts = parseInt(String(options.maxAttempts || 3), 10); // 기본 최대 3회 시도
|
|
81
103
|
// verbose 로그 헬퍼
|
|
82
104
|
const verboseLog = (message) => {
|
|
@@ -108,6 +130,37 @@ export async function auto(count = '5', options = {}) {
|
|
|
108
130
|
const startTime = Date.now();
|
|
109
131
|
const sessionId = generateSessionId();
|
|
110
132
|
const sessionStartTime = new Date().toISOString();
|
|
133
|
+
const processedInDryRun = new Set(); // dry-run 모드에서 처리된 feature 추적
|
|
134
|
+
// 서버 연결 초기화
|
|
135
|
+
const connection = loadConnection(baseDir);
|
|
136
|
+
const client = getApiClient();
|
|
137
|
+
const serverCtx = {
|
|
138
|
+
connected: false,
|
|
139
|
+
};
|
|
140
|
+
// 서버에 세션 시작 알림 (연결된 경우)
|
|
141
|
+
if (connection && client) {
|
|
142
|
+
try {
|
|
143
|
+
const sessionInfo = await client.startSession(connection.projectId, {
|
|
144
|
+
agentType: 'cli',
|
|
145
|
+
notes: `Auto mode started: ${isLoopMode ? 'loop' : `batch (${maxFeatures})`}`,
|
|
146
|
+
});
|
|
147
|
+
serverCtx.connected = true;
|
|
148
|
+
serverCtx.projectId = connection.projectId;
|
|
149
|
+
serverCtx.sessionId = sessionInfo.id;
|
|
150
|
+
serverCtx.client = client;
|
|
151
|
+
// 현재 세션 정보 저장
|
|
152
|
+
saveCurrentSession({
|
|
153
|
+
id: sessionInfo.id,
|
|
154
|
+
projectId: sessionInfo.projectId,
|
|
155
|
+
startedAt: sessionInfo.startedAt,
|
|
156
|
+
agentType: 'cli',
|
|
157
|
+
}, baseDir);
|
|
158
|
+
printInfo(colors.dim(`서버 세션 시작됨 (ID: ${sessionInfo.id.slice(0, 8)}...)`));
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
printWarning('서버 세션 등록 실패 (로컬 모드로 계속)');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
111
164
|
// Ctrl+C 핸들링 (loop 모드용)
|
|
112
165
|
let shouldStop = false;
|
|
113
166
|
const handleSignal = () => {
|
|
@@ -167,13 +220,13 @@ export async function auto(count = '5', options = {}) {
|
|
|
167
220
|
verboseLog(`Feature ${interruptedWork.feature.id} exceeded max attempts (${maxAttempts}), moving to next`);
|
|
168
221
|
appendToProgress(baseDir, `- [!] ${interruptedWork.feature.id}: Permanently failed after ${maxAttempts} attempts\n`);
|
|
169
222
|
clearSessionContextUtil(baseDir); // 컨텍스트 정리하고 다음 feature로
|
|
170
|
-
feature = selectNextFeature(currentFeatureList, options);
|
|
223
|
+
feature = selectNextFeature(currentFeatureList, options, processedInDryRun);
|
|
171
224
|
featureContext = { attemptNumber: 1 };
|
|
172
225
|
}
|
|
173
226
|
}
|
|
174
227
|
else {
|
|
175
228
|
// 중단된 작업 없음 - 다음 feature 선택
|
|
176
|
-
feature = selectNextFeature(currentFeatureList, options);
|
|
229
|
+
feature = selectNextFeature(currentFeatureList, options, processedInDryRun);
|
|
177
230
|
featureContext = { attemptNumber: 1 };
|
|
178
231
|
if (feature) {
|
|
179
232
|
verboseLog(`Selected next feature: ${feature.id}`);
|
|
@@ -208,6 +261,7 @@ export async function auto(count = '5', options = {}) {
|
|
|
208
261
|
status: 'skipped',
|
|
209
262
|
duration: 0,
|
|
210
263
|
});
|
|
264
|
+
processedInDryRun.add(feature.id); // dry-run에서 처리된 feature 추적
|
|
211
265
|
continue;
|
|
212
266
|
}
|
|
213
267
|
try {
|
|
@@ -216,12 +270,18 @@ export async function auto(count = '5', options = {}) {
|
|
|
216
270
|
status: 'in_progress',
|
|
217
271
|
attempts: featureContext?.attemptNumber || 1,
|
|
218
272
|
});
|
|
273
|
+
// 서버에 feature 시작 로그 전송
|
|
274
|
+
await sendFeatureLog(serverCtx, feature.id, 'started', `Started working on ${feature.id}: ${feature.description}`, {
|
|
275
|
+
attemptNumber: featureContext?.attemptNumber || 1,
|
|
276
|
+
priority: feature.priority,
|
|
277
|
+
category: feature.category,
|
|
278
|
+
});
|
|
219
279
|
// 2. Build enhanced prompt context
|
|
220
280
|
verboseLog(`Building prompt context for ${feature.id}`);
|
|
221
281
|
const promptContext = await buildPromptContext(baseDir, feature, currentFeatureList, featureContext?.attemptNumber || 1, featureContext?.previousError);
|
|
222
|
-
// 3. Claude Code로 구현 시도
|
|
282
|
+
// 3. Claude Code로 구현 시도 (SDK 사용)
|
|
223
283
|
console.log(` ${icons.arrow} ${colors.cyan('work')} ${feature.id}`);
|
|
224
|
-
verboseLog(`Starting Claude
|
|
284
|
+
verboseLog(`Starting Claude SDK for ${feature.id}`);
|
|
225
285
|
const implementStart = Date.now();
|
|
226
286
|
const implementResult = await runClaudeCode(baseDir, feature, promptContext, claudeTimeout, isVerbose);
|
|
227
287
|
if (!implementResult.success) {
|
|
@@ -231,6 +291,8 @@ export async function auto(count = '5', options = {}) {
|
|
|
231
291
|
const implementDuration = Date.now() - implementStart;
|
|
232
292
|
console.log(` ${icons.success} work completed (${formatDuration(implementDuration)})`);
|
|
233
293
|
verboseLog('Implementation completed successfully');
|
|
294
|
+
// 서버에 progress 로그 전송
|
|
295
|
+
await sendFeatureLog(serverCtx, feature.id, 'progress', `Implementation completed in ${formatDuration(implementDuration)}`, { phase: 'implementation', duration: implementDuration });
|
|
234
296
|
// 2. Self-Verification
|
|
235
297
|
console.log(` ${icons.arrow} ${colors.cyan('verify')} ${feature.id}`);
|
|
236
298
|
verboseLog('Running self-verification of acceptance criteria');
|
|
@@ -247,6 +309,8 @@ export async function auto(count = '5', options = {}) {
|
|
|
247
309
|
const verifyDuration = Date.now() - verifyStart;
|
|
248
310
|
console.log(` ${icons.success} verify passed (${formatDuration(verifyDuration)})`);
|
|
249
311
|
verboseLog(`All ${feature.acceptanceCriteria.length} criteria passed verification`);
|
|
312
|
+
// 서버에 verification 로그 전송
|
|
313
|
+
await sendFeatureLog(serverCtx, feature.id, 'progress', `Self-verification passed (${feature.acceptanceCriteria.length} criteria)`, { phase: 'verification', duration: verifyDuration });
|
|
250
314
|
// 3. 테스트 실행
|
|
251
315
|
console.log(` ${icons.arrow} ${colors.cyan('test')} ${feature.id}`);
|
|
252
316
|
const testStart = Date.now();
|
|
@@ -256,6 +320,8 @@ export async function auto(count = '5', options = {}) {
|
|
|
256
320
|
}
|
|
257
321
|
const testDuration = Date.now() - testStart;
|
|
258
322
|
console.log(` ${icons.success} test passed (${formatDuration(testDuration)})`);
|
|
323
|
+
// 서버에 test passed 로그 전송
|
|
324
|
+
await sendFeatureLog(serverCtx, feature.id, 'test_passed', `Tests passed in ${formatDuration(testDuration)}`, { phase: 'test', duration: testDuration, testCommand: feature.testCommand });
|
|
259
325
|
// 4. 완료 처리
|
|
260
326
|
console.log(` ${icons.arrow} ${colors.cyan('complete')} ${feature.id}`);
|
|
261
327
|
await markComplete(baseDir, feature.id);
|
|
@@ -272,10 +338,17 @@ export async function auto(count = '5', options = {}) {
|
|
|
272
338
|
console.log(` ${colors.warning('⚠')} commit failed (continuing anyway)`);
|
|
273
339
|
}
|
|
274
340
|
completedCount++;
|
|
341
|
+
const featureDuration = Date.now() - featureStartTime;
|
|
275
342
|
results.push({
|
|
276
343
|
featureId: feature.id,
|
|
277
344
|
status: 'success',
|
|
278
|
-
duration:
|
|
345
|
+
duration: featureDuration,
|
|
346
|
+
});
|
|
347
|
+
// 서버에 feature 완료 로그 전송
|
|
348
|
+
await sendFeatureLog(serverCtx, feature.id, 'completed', `Feature ${feature.id} completed successfully in ${formatDuration(featureDuration)}`, {
|
|
349
|
+
duration: featureDuration,
|
|
350
|
+
commitHash: commitResult.hash,
|
|
351
|
+
attemptNumber: featureContext?.attemptNumber || 1,
|
|
279
352
|
});
|
|
280
353
|
// Clear session context (success)
|
|
281
354
|
clearSessionContextUtil(baseDir);
|
|
@@ -290,6 +363,12 @@ export async function auto(count = '5', options = {}) {
|
|
|
290
363
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
291
364
|
// Analyze failure pattern
|
|
292
365
|
const failureAnalysis = analyzeFailure(errorMessage);
|
|
366
|
+
// 서버에 실패 로그 전송
|
|
367
|
+
await sendFeatureLog(serverCtx, feature.id, 'failed', `Feature ${feature.id} failed: ${errorMessage}`, {
|
|
368
|
+
attemptNumber: featureContext?.attemptNumber || 1,
|
|
369
|
+
failureCategory: failureAnalysis.category,
|
|
370
|
+
willRetry: (featureContext?.attemptNumber || 1) < maxAttempts && failureAnalysis.shouldRetry,
|
|
371
|
+
});
|
|
293
372
|
// Display failure info
|
|
294
373
|
console.log(` ${icons.error} Failed: ${errorMessage}`);
|
|
295
374
|
// Verbose: show failure analysis
|
|
@@ -332,12 +411,35 @@ export async function auto(count = '5', options = {}) {
|
|
|
332
411
|
}
|
|
333
412
|
// 최종 결과 출력
|
|
334
413
|
const totalDuration = Date.now() - startTime;
|
|
414
|
+
const totalDurationSeconds = Math.floor(totalDuration / 1000);
|
|
335
415
|
const sessionEndTime = new Date().toISOString();
|
|
336
416
|
const finalProgress = getProgress(baseDir);
|
|
337
417
|
// 성공/실패한 features 분류
|
|
338
418
|
const completedFeatures = results.filter((r) => r.status === 'success');
|
|
339
419
|
const failedFeatures = results.filter((r) => r.status === 'failed');
|
|
340
420
|
const skippedFeatures = results.filter((r) => r.status === 'skipped');
|
|
421
|
+
// 서버에 세션 종료 알림 (연결된 경우)
|
|
422
|
+
if (serverCtx.connected && serverCtx.client && serverCtx.sessionId) {
|
|
423
|
+
try {
|
|
424
|
+
await serverCtx.client.endSession(serverCtx.sessionId, {
|
|
425
|
+
workDone: completedFeatures.map((r) => `${r.featureId}: Completed automatically`),
|
|
426
|
+
commits: [], // 커밋 해시는 개별 feature에서 처리됨
|
|
427
|
+
nextSteps: failedFeatures.map((r) => `Retry ${r.featureId}: ${r.error?.slice(0, 50)}`),
|
|
428
|
+
notes: [
|
|
429
|
+
`Auto mode: ${isLoopMode ? 'loop' : `batch (${maxFeatures})`}`,
|
|
430
|
+
`Progress: ${initialProgress.percentage}% → ${finalProgress.percentage}%`,
|
|
431
|
+
`Success rate: ${processedCount > 0 ? Math.round((completedCount / processedCount) * 100) : 0}%`,
|
|
432
|
+
],
|
|
433
|
+
duration: totalDurationSeconds,
|
|
434
|
+
});
|
|
435
|
+
// 현재 세션 정보 삭제
|
|
436
|
+
clearCurrentSession(baseDir);
|
|
437
|
+
printInfo(colors.dim(`서버 세션 종료됨 (${formatDurationKo(totalDurationSeconds)})`));
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
printWarning('서버 세션 종료 알림 실패');
|
|
441
|
+
}
|
|
442
|
+
}
|
|
341
443
|
printSection('Summary');
|
|
342
444
|
console.log('');
|
|
343
445
|
console.log(` ${colors.success('Completed:')} ${completedCount}`);
|
|
@@ -382,19 +484,24 @@ ${skippedFeatures.map((r) => `- **${r.featureId}**`).join('\n')}
|
|
|
382
484
|
printWarning('Auto mode finished with failures');
|
|
383
485
|
}
|
|
384
486
|
}
|
|
385
|
-
function selectNextFeature(featureList, options) {
|
|
487
|
+
function selectNextFeature(featureList, options, excludeIds) {
|
|
386
488
|
let candidates = featureList.features.filter((f) => !f.passes);
|
|
489
|
+
// dry-run 모드에서 이미 처리된 feature 제외
|
|
490
|
+
if (excludeIds && excludeIds.size > 0) {
|
|
491
|
+
candidates = candidates.filter((f) => !excludeIds.has(f.id));
|
|
492
|
+
}
|
|
387
493
|
// 카테고리 필터
|
|
388
494
|
if (options.category) {
|
|
389
495
|
candidates = candidates.filter((f) => f.category === options.category);
|
|
390
496
|
}
|
|
391
|
-
// 의존성 확인
|
|
497
|
+
// 의존성 확인 (dry-run에서는 처리된 것도 완료로 간주)
|
|
392
498
|
candidates = candidates.filter((f) => {
|
|
393
499
|
if (!f.dependsOn || f.dependsOn.length === 0)
|
|
394
500
|
return true;
|
|
395
501
|
return f.dependsOn.every((depId) => {
|
|
396
502
|
const dep = featureList.features.find((df) => df.id === depId);
|
|
397
|
-
|
|
503
|
+
// 실제로 passes이거나, dry-run에서 처리된 경우
|
|
504
|
+
return dep?.passes === true || (excludeIds && excludeIds.has(depId));
|
|
398
505
|
});
|
|
399
506
|
});
|
|
400
507
|
// 우선순위로 정렬
|
|
@@ -402,100 +509,62 @@ function selectNextFeature(featureList, options) {
|
|
|
402
509
|
return candidates[0];
|
|
403
510
|
}
|
|
404
511
|
async function runClaudeCode(baseDir, feature, context, timeout, verbose) {
|
|
405
|
-
|
|
512
|
+
// Two-Agent Pattern: 새로운 간결한 프롬프트 사용
|
|
513
|
+
const progress = getProgress(baseDir);
|
|
514
|
+
const codingContext = {
|
|
515
|
+
projectDir: context.workingDirectory,
|
|
516
|
+
recentCommits: context.recentCommits,
|
|
517
|
+
recentProgress: context.recentProgress,
|
|
518
|
+
completedFeatures: progress.completed,
|
|
519
|
+
totalFeatures: progress.total,
|
|
520
|
+
attemptNumber: context.attemptNumber,
|
|
521
|
+
previousError: context.previousError,
|
|
522
|
+
dependencies: context.dependencies,
|
|
523
|
+
};
|
|
524
|
+
const prompt = getFeatureImplementationPrompt(feature, codingContext);
|
|
406
525
|
// verbose 로그 헬퍼
|
|
407
526
|
const verboseLog = (message) => {
|
|
408
527
|
if (verbose) {
|
|
409
528
|
console.log(colors.dim(` [verbose] ${message}`));
|
|
410
529
|
}
|
|
411
530
|
};
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
434
|
-
env: {
|
|
435
|
-
...process.env,
|
|
436
|
-
MCP_TIMEOUT: mcpTimeout,
|
|
531
|
+
// Claude Agent SDK 사용
|
|
532
|
+
verboseLog('Using Claude Agent SDK');
|
|
533
|
+
try {
|
|
534
|
+
const result = await runFeatureImplementation(feature, prompt, {
|
|
535
|
+
workingDirectory: baseDir,
|
|
536
|
+
timeout,
|
|
537
|
+
maxTurns: 200,
|
|
538
|
+
securityConfig: SECURITY_PRESET_DEVELOPMENT,
|
|
539
|
+
onLog: verboseLog,
|
|
540
|
+
onProgress: (message) => {
|
|
541
|
+
if (verbose && message.type === 'assistant') {
|
|
542
|
+
// 어시스턴트 메시지 실시간 출력
|
|
543
|
+
const content = message.message?.content;
|
|
544
|
+
if (Array.isArray(content)) {
|
|
545
|
+
for (const block of content) {
|
|
546
|
+
if (block && typeof block === 'object' && 'type' in block && block.type === 'text' && 'text' in block) {
|
|
547
|
+
process.stdout.write(block.text);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
437
552
|
},
|
|
438
553
|
});
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
if (claude.stdout) {
|
|
443
|
-
claude.stdout.on('data', (data) => {
|
|
444
|
-
const chunk = data.toString();
|
|
445
|
-
stdout += chunk;
|
|
446
|
-
if (verbose) {
|
|
447
|
-
process.stdout.write(chunk);
|
|
448
|
-
}
|
|
449
|
-
});
|
|
554
|
+
if (result.success) {
|
|
555
|
+
verboseLog(`SDK execution completed: turns=${result.numTurns}, cost=$${result.costUsd?.toFixed(4)}`);
|
|
556
|
+
return { success: true, output: result.result };
|
|
450
557
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
const chunk = data.toString();
|
|
455
|
-
stderr += chunk;
|
|
456
|
-
if (verbose) {
|
|
457
|
-
process.stderr.write(chunk);
|
|
458
|
-
}
|
|
459
|
-
});
|
|
558
|
+
else {
|
|
559
|
+
verboseLog(`SDK execution failed: ${result.error}`);
|
|
560
|
+
return { success: false, error: result.error, output: result.result };
|
|
460
561
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
verboseLog('Process still alive, sending SIGKILL...');
|
|
468
|
-
claude.kill('SIGKILL');
|
|
469
|
-
}
|
|
470
|
-
}, 5000);
|
|
471
|
-
const errorMsg = `Timeout after ${timeout / 1000}s`;
|
|
472
|
-
if (stderr) {
|
|
473
|
-
verboseLog(`Last stderr: ${stderr.slice(-500)}`);
|
|
474
|
-
}
|
|
475
|
-
resolve({ success: false, error: errorMsg, output: stdout + stderr });
|
|
476
|
-
}, timeout);
|
|
477
|
-
claude.on('close', (code, signal) => {
|
|
478
|
-
clearTimeout(timeoutId);
|
|
479
|
-
verboseLog(`Process exited with code=${code}, signal=${signal}`);
|
|
480
|
-
if (code === 0) {
|
|
481
|
-
resolve({ success: true, output: stdout });
|
|
482
|
-
}
|
|
483
|
-
else {
|
|
484
|
-
const errorDetail = signal
|
|
485
|
-
? `Killed by signal ${signal}`
|
|
486
|
-
: `Exit code ${code}`;
|
|
487
|
-
if (stderr) {
|
|
488
|
-
verboseLog(`stderr: ${stderr.slice(-500)}`);
|
|
489
|
-
}
|
|
490
|
-
resolve({ success: false, error: errorDetail, output: stdout + stderr });
|
|
491
|
-
}
|
|
492
|
-
});
|
|
493
|
-
claude.on('error', (err) => {
|
|
494
|
-
clearTimeout(timeoutId);
|
|
495
|
-
verboseLog(`Spawn error: ${err.message}`);
|
|
496
|
-
resolve({ success: false, error: err.message });
|
|
497
|
-
});
|
|
498
|
-
});
|
|
562
|
+
}
|
|
563
|
+
catch (error) {
|
|
564
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
565
|
+
verboseLog(`SDK execution error: ${errorMessage}`);
|
|
566
|
+
return { success: false, error: errorMessage };
|
|
567
|
+
}
|
|
499
568
|
}
|
|
500
569
|
async function runTest(baseDir, feature) {
|
|
501
570
|
const testCommand = feature.testCommand || 'bun test';
|
|
@@ -511,15 +580,15 @@ async function runTest(baseDir, feature) {
|
|
|
511
580
|
resolve({ success: false });
|
|
512
581
|
return;
|
|
513
582
|
}
|
|
514
|
-
// UI 카테고리인 경우 브라우저 테스트 추가 실행
|
|
515
|
-
if (feature.category === 'ui') {
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
}
|
|
583
|
+
// UI 카테고리인 경우 브라우저 테스트 추가 실행 (auto 모드에서 비활성화)
|
|
584
|
+
// if (feature.category === 'ui') {
|
|
585
|
+
// console.log(` ${icons.arrow} ${colors.cyan('browser-test')} ${feature.id}`);
|
|
586
|
+
// const browserTestResult = await runBrowserTest(baseDir, feature);
|
|
587
|
+
// resolve({ success: browserTestResult.success });
|
|
588
|
+
// } else {
|
|
589
|
+
// resolve({ success: true });
|
|
590
|
+
// }
|
|
591
|
+
resolve({ success: true });
|
|
523
592
|
});
|
|
524
593
|
test.on('error', () => {
|
|
525
594
|
resolve({ success: false });
|
|
@@ -532,7 +601,7 @@ async function runTest(baseDir, feature) {
|
|
|
532
601
|
});
|
|
533
602
|
}
|
|
534
603
|
/**
|
|
535
|
-
* Run browser test using Puppeteer MCP server
|
|
604
|
+
* Run browser test using Puppeteer MCP server (SDK 사용)
|
|
536
605
|
* UI 카테고리 feature에 대해 자동으로 실행
|
|
537
606
|
*/
|
|
538
607
|
async function runBrowserTest(baseDir, feature) {
|
|
@@ -547,7 +616,7 @@ async function runBrowserTest(baseDir, feature) {
|
|
|
547
616
|
const htmlPath = path.join(baseDir, htmlFile);
|
|
548
617
|
const fileUrl = `file://${htmlPath}`;
|
|
549
618
|
console.log(` ${colors.dim(` Opening ${htmlFile} in browser...`)}`);
|
|
550
|
-
// Use Claude
|
|
619
|
+
// Use Claude Agent SDK to run Puppeteer test via MCP
|
|
551
620
|
const browserTestPrompt = `
|
|
552
621
|
# Browser Test for ${feature.id}
|
|
553
622
|
|
|
@@ -567,7 +636,13 @@ ${feature.acceptanceCriteria.map((c, i) => ` ${i + 1}. ${c}`).join('\n')}
|
|
|
567
636
|
|
|
568
637
|
If the page fails to load or doesn't meet the criteria, report the issue.
|
|
569
638
|
`;
|
|
570
|
-
const result = await
|
|
639
|
+
const result = await runAgent({
|
|
640
|
+
workingDirectory: baseDir,
|
|
641
|
+
prompt: browserTestPrompt,
|
|
642
|
+
timeout: 60000, // 60 seconds for browser tests
|
|
643
|
+
maxTurns: 30,
|
|
644
|
+
securityConfig: SECURITY_PRESET_DEVELOPMENT,
|
|
645
|
+
});
|
|
571
646
|
if (!result.success) {
|
|
572
647
|
console.log(` ${colors.error('✗')} browser test failed: ${result.error}`);
|
|
573
648
|
return { success: false, error: result.error };
|
|
@@ -581,61 +656,6 @@ If the page fails to load or doesn't meet the criteria, report the issue.
|
|
|
581
656
|
return { success: true }; // Don't fail the whole feature for browser test errors
|
|
582
657
|
}
|
|
583
658
|
}
|
|
584
|
-
/**
|
|
585
|
-
* Run Claude Code specifically for browser testing
|
|
586
|
-
*/
|
|
587
|
-
async function runClaudeCodeForBrowserTest(baseDir, prompt) {
|
|
588
|
-
return new Promise((resolve) => {
|
|
589
|
-
const args = [
|
|
590
|
-
'--print',
|
|
591
|
-
'--dangerously-skip-permissions',
|
|
592
|
-
prompt,
|
|
593
|
-
];
|
|
594
|
-
const claude = spawn('claude', args, {
|
|
595
|
-
cwd: baseDir,
|
|
596
|
-
shell: false,
|
|
597
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
598
|
-
env: {
|
|
599
|
-
...process.env,
|
|
600
|
-
MCP_TIMEOUT: '30000', // 30 seconds for browser operations
|
|
601
|
-
},
|
|
602
|
-
});
|
|
603
|
-
let stdout = '';
|
|
604
|
-
let stderr = '';
|
|
605
|
-
if (claude.stdout) {
|
|
606
|
-
claude.stdout.on('data', (data) => {
|
|
607
|
-
stdout += data.toString();
|
|
608
|
-
});
|
|
609
|
-
}
|
|
610
|
-
if (claude.stderr) {
|
|
611
|
-
claude.stderr.on('data', (data) => {
|
|
612
|
-
stderr += data.toString();
|
|
613
|
-
});
|
|
614
|
-
}
|
|
615
|
-
const timeoutId = setTimeout(() => {
|
|
616
|
-
claude.kill('SIGTERM');
|
|
617
|
-
setTimeout(() => {
|
|
618
|
-
if (!claude.killed) {
|
|
619
|
-
claude.kill('SIGKILL');
|
|
620
|
-
}
|
|
621
|
-
}, 5000);
|
|
622
|
-
resolve({ success: false, error: 'Browser test timeout' });
|
|
623
|
-
}, 60000); // 60 second timeout for browser tests
|
|
624
|
-
claude.on('close', (code) => {
|
|
625
|
-
clearTimeout(timeoutId);
|
|
626
|
-
if (code === 0) {
|
|
627
|
-
resolve({ success: true, output: stdout });
|
|
628
|
-
}
|
|
629
|
-
else {
|
|
630
|
-
resolve({ success: false, error: `Exit code ${code}`, output: stdout + stderr });
|
|
631
|
-
}
|
|
632
|
-
});
|
|
633
|
-
claude.on('error', (err) => {
|
|
634
|
-
clearTimeout(timeoutId);
|
|
635
|
-
resolve({ success: false, error: err.message });
|
|
636
|
-
});
|
|
637
|
-
});
|
|
638
|
-
}
|
|
639
659
|
async function markComplete(baseDir, featureId) {
|
|
640
660
|
const featureList = readFeatureList(baseDir);
|
|
641
661
|
if (!featureList)
|
|
@@ -774,7 +794,28 @@ FINAL VERDICT: [ALL PASS / FAILED]
|
|
|
774
794
|
|
|
775
795
|
Start verification now.
|
|
776
796
|
`;
|
|
777
|
-
|
|
797
|
+
// Claude Agent SDK를 사용한 검증
|
|
798
|
+
verboseLog('Running verification via SDK');
|
|
799
|
+
const result = await runAgent({
|
|
800
|
+
workingDirectory: baseDir,
|
|
801
|
+
prompt: verificationPrompt,
|
|
802
|
+
timeout,
|
|
803
|
+
maxTurns: 50,
|
|
804
|
+
securityConfig: SECURITY_PRESET_DEVELOPMENT,
|
|
805
|
+
onLog: verboseLog,
|
|
806
|
+
onProgress: (message) => {
|
|
807
|
+
if (verbose && message.type === 'assistant') {
|
|
808
|
+
const content = message.message?.content;
|
|
809
|
+
if (Array.isArray(content)) {
|
|
810
|
+
for (const block of content) {
|
|
811
|
+
if (block && typeof block === 'object' && 'type' in block && block.type === 'text' && 'text' in block) {
|
|
812
|
+
process.stdout.write(block.text);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
},
|
|
818
|
+
});
|
|
778
819
|
if (!result.success) {
|
|
779
820
|
verboseLog(`Verification failed to run: ${result.error}`);
|
|
780
821
|
return {
|
|
@@ -788,80 +829,7 @@ Start verification now.
|
|
|
788
829
|
};
|
|
789
830
|
}
|
|
790
831
|
// Parse verification output
|
|
791
|
-
return parseVerificationOutput(result.
|
|
792
|
-
}
|
|
793
|
-
/**
|
|
794
|
-
* Run Claude Code specifically for verification (separate from implementation)
|
|
795
|
-
*/
|
|
796
|
-
async function runClaudeCodeForVerification(baseDir, prompt, timeout, verbose) {
|
|
797
|
-
const verboseLog = (message) => {
|
|
798
|
-
if (verbose) {
|
|
799
|
-
console.log(colors.dim(` [verbose] ${message}`));
|
|
800
|
-
}
|
|
801
|
-
};
|
|
802
|
-
return new Promise((resolve) => {
|
|
803
|
-
const args = [
|
|
804
|
-
'--print',
|
|
805
|
-
'--dangerously-skip-permissions',
|
|
806
|
-
'--strict-mcp-config',
|
|
807
|
-
prompt,
|
|
808
|
-
];
|
|
809
|
-
verboseLog(`Spawning claude for verification`);
|
|
810
|
-
const claude = spawn('claude', args, {
|
|
811
|
-
cwd: baseDir,
|
|
812
|
-
shell: false,
|
|
813
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
814
|
-
env: {
|
|
815
|
-
...process.env,
|
|
816
|
-
MCP_TIMEOUT: '10000',
|
|
817
|
-
},
|
|
818
|
-
});
|
|
819
|
-
let stdout = '';
|
|
820
|
-
let stderr = '';
|
|
821
|
-
if (claude.stdout) {
|
|
822
|
-
claude.stdout.on('data', (data) => {
|
|
823
|
-
const chunk = data.toString();
|
|
824
|
-
stdout += chunk;
|
|
825
|
-
if (verbose) {
|
|
826
|
-
process.stdout.write(chunk);
|
|
827
|
-
}
|
|
828
|
-
});
|
|
829
|
-
}
|
|
830
|
-
if (claude.stderr) {
|
|
831
|
-
claude.stderr.on('data', (data) => {
|
|
832
|
-
const chunk = data.toString();
|
|
833
|
-
stderr += chunk;
|
|
834
|
-
if (verbose) {
|
|
835
|
-
process.stderr.write(chunk);
|
|
836
|
-
}
|
|
837
|
-
});
|
|
838
|
-
}
|
|
839
|
-
const timeoutId = setTimeout(() => {
|
|
840
|
-
verboseLog('Verification timeout, killing process...');
|
|
841
|
-
claude.kill('SIGTERM');
|
|
842
|
-
setTimeout(() => {
|
|
843
|
-
if (!claude.killed) {
|
|
844
|
-
claude.kill('SIGKILL');
|
|
845
|
-
}
|
|
846
|
-
}, 5000);
|
|
847
|
-
resolve({ success: false, error: `Verification timeout after ${timeout / 1000}s` });
|
|
848
|
-
}, timeout);
|
|
849
|
-
claude.on('close', (code) => {
|
|
850
|
-
clearTimeout(timeoutId);
|
|
851
|
-
verboseLog(`Verification process exited with code=${code}`);
|
|
852
|
-
if (code === 0) {
|
|
853
|
-
resolve({ success: true, output: stdout });
|
|
854
|
-
}
|
|
855
|
-
else {
|
|
856
|
-
resolve({ success: false, error: `Exit code ${code}`, output: stdout + stderr });
|
|
857
|
-
}
|
|
858
|
-
});
|
|
859
|
-
claude.on('error', (err) => {
|
|
860
|
-
clearTimeout(timeoutId);
|
|
861
|
-
verboseLog(`Verification spawn error: ${err.message}`);
|
|
862
|
-
resolve({ success: false, error: err.message });
|
|
863
|
-
});
|
|
864
|
-
});
|
|
832
|
+
return parseVerificationOutput(result.result || '', feature.acceptanceCriteria);
|
|
865
833
|
}
|
|
866
834
|
/**
|
|
867
835
|
* Parse verification output from Claude
|
|
@@ -1124,6 +1092,23 @@ function formatDuration(ms) {
|
|
|
1124
1092
|
return `${seconds}s`;
|
|
1125
1093
|
}
|
|
1126
1094
|
}
|
|
1095
|
+
/**
|
|
1096
|
+
* 시간 포맷팅 (한글, 초 단위 입력)
|
|
1097
|
+
*/
|
|
1098
|
+
function formatDurationKo(seconds) {
|
|
1099
|
+
const hours = Math.floor(seconds / 3600);
|
|
1100
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
1101
|
+
const secs = seconds % 60;
|
|
1102
|
+
if (hours > 0) {
|
|
1103
|
+
return `${hours}시간 ${minutes}분`;
|
|
1104
|
+
}
|
|
1105
|
+
else if (minutes > 0) {
|
|
1106
|
+
return `${minutes}분 ${secs}초`;
|
|
1107
|
+
}
|
|
1108
|
+
else {
|
|
1109
|
+
return `${secs}초`;
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1127
1112
|
/**
|
|
1128
1113
|
* Analyze failure error message and match against known patterns
|
|
1129
1114
|
*/
|
|
@@ -1159,4 +1144,104 @@ function generateSessionId() {
|
|
|
1159
1144
|
const seconds = String(now.getSeconds()).padStart(2, '0');
|
|
1160
1145
|
return `${year}${month}${day}-${hours}${minutes}${seconds}`;
|
|
1161
1146
|
}
|
|
1147
|
+
/**
|
|
1148
|
+
* 첫 실행 여부 감지
|
|
1149
|
+
* - feature_list.json이 없거나 비어있으면 첫 실행
|
|
1150
|
+
* - 모든 feature가 미완료 상태면 첫 실행으로 간주
|
|
1151
|
+
*/
|
|
1152
|
+
function isFirstRun(baseDir) {
|
|
1153
|
+
const featureListPath = path.join(baseDir, '.aiag', 'feature_list.json');
|
|
1154
|
+
const progressPath = path.join(baseDir, '.aiag', 'progress.md');
|
|
1155
|
+
// feature_list.json이 없으면 첫 실행
|
|
1156
|
+
if (!fs.existsSync(featureListPath))
|
|
1157
|
+
return true;
|
|
1158
|
+
const featureList = readFeatureList(baseDir);
|
|
1159
|
+
// feature_list.json이 없거나 features가 비어있으면 첫 실행
|
|
1160
|
+
if (!featureList || featureList.features.length === 0)
|
|
1161
|
+
return true;
|
|
1162
|
+
// 모든 feature가 미완료 상태면 첫 실행으로 간주
|
|
1163
|
+
const hasAnyProgress = featureList.features.some((f) => f.passes);
|
|
1164
|
+
if (!hasAnyProgress) {
|
|
1165
|
+
// progress.md가 없으면 확실히 첫 실행
|
|
1166
|
+
if (!fs.existsSync(progressPath))
|
|
1167
|
+
return true;
|
|
1168
|
+
// progress.md가 있지만 내용이 초기 상태면 첫 실행
|
|
1169
|
+
try {
|
|
1170
|
+
const progressContent = fs.readFileSync(progressPath, 'utf-8');
|
|
1171
|
+
// 실질적인 세션 로그가 없으면 첫 실행
|
|
1172
|
+
if (!progressContent.includes('## Auto Session:'))
|
|
1173
|
+
return true;
|
|
1174
|
+
}
|
|
1175
|
+
catch {
|
|
1176
|
+
return true;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
return false;
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* PRD 파일 읽기 (있는 경우)
|
|
1183
|
+
*/
|
|
1184
|
+
function readPrdContent(baseDir) {
|
|
1185
|
+
const prdPaths = [
|
|
1186
|
+
path.join(baseDir, '.aiag', 'prd.md'),
|
|
1187
|
+
path.join(baseDir, 'PRD.md'),
|
|
1188
|
+
path.join(baseDir, 'prd.md'),
|
|
1189
|
+
path.join(baseDir, 'docs', 'prd.md'),
|
|
1190
|
+
path.join(baseDir, 'docs', 'PRD.md'),
|
|
1191
|
+
];
|
|
1192
|
+
for (const prdPath of prdPaths) {
|
|
1193
|
+
if (fs.existsSync(prdPath)) {
|
|
1194
|
+
try {
|
|
1195
|
+
return fs.readFileSync(prdPath, 'utf-8');
|
|
1196
|
+
}
|
|
1197
|
+
catch {
|
|
1198
|
+
continue;
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
return undefined;
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* Initializer 세션 실행 (SDK 사용)
|
|
1206
|
+
* 첫 실행 시 프로젝트 초기화를 위해 호출
|
|
1207
|
+
*/
|
|
1208
|
+
async function runInitializerSession(baseDir, timeout, verbose) {
|
|
1209
|
+
const projectName = path.basename(baseDir);
|
|
1210
|
+
const featureList = readFeatureList(baseDir);
|
|
1211
|
+
const prdContent = readPrdContent(baseDir);
|
|
1212
|
+
const context = {
|
|
1213
|
+
projectDir: baseDir,
|
|
1214
|
+
projectName,
|
|
1215
|
+
hasExistingFeatures: featureList !== null && featureList.features.length > 0,
|
|
1216
|
+
featureCount: featureList?.features.length || 0,
|
|
1217
|
+
};
|
|
1218
|
+
const prompt = getInitializerPrompt(context, prdContent);
|
|
1219
|
+
const verboseLog = (message) => {
|
|
1220
|
+
if (verbose) {
|
|
1221
|
+
console.log(colors.dim(` [verbose] ${message}`));
|
|
1222
|
+
}
|
|
1223
|
+
};
|
|
1224
|
+
verboseLog('Running Initializer Agent via SDK');
|
|
1225
|
+
verboseLog(`PRD found: ${prdContent ? 'Yes' : 'No'}`);
|
|
1226
|
+
// SDK의 runInitializer 함수 사용
|
|
1227
|
+
const result = await runInitializer(prompt, {
|
|
1228
|
+
workingDirectory: baseDir,
|
|
1229
|
+
timeout,
|
|
1230
|
+
securityConfig: SECURITY_PRESET_DEVELOPMENT,
|
|
1231
|
+
onLog: verboseLog,
|
|
1232
|
+
onProgress: (message) => {
|
|
1233
|
+
if (verbose && message.type === 'assistant') {
|
|
1234
|
+
const content = message.message?.content;
|
|
1235
|
+
if (Array.isArray(content)) {
|
|
1236
|
+
for (const block of content) {
|
|
1237
|
+
if (block && typeof block === 'object' && 'type' in block && block.type === 'text' && 'text' in block) {
|
|
1238
|
+
process.stdout.write(block.text);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
},
|
|
1244
|
+
});
|
|
1245
|
+
return { success: result.success, error: result.error };
|
|
1246
|
+
}
|
|
1162
1247
|
//# sourceMappingURL=auto.js.map
|