feat: v2.2.92 → v2.2.158 — god-file 분해 + Stocks feature + 대화 연속성
R56–R59: agent.ts 2731→1529줄 god-file 분해 (25 modules) · attrParsers + LLM 메서드 8개 (callNonStreaming, streamChatOnce 등) · executeActions 415줄 → 8 handler 그룹 (file/run/list/brain/calendar/sheets/tasks) · handlePrompt 1100줄 → 7 phase 모듈 (system prompt + budget + autoContinue 등) R50–R55: extension.ts 1145→349줄 (telegram/settings/provider commands 분리) Stocks feature 신규: /stocks slash command (v2.2.152~158) · .astra/stocks.json 저장소 + Yahoo Finance 현재가 갱신 · 8 키워드 필터 (ROE/성장성/유동성/수익성/영업효율/기술력/안정성/PBR) · Naver 시가총액 페이지 JSON API (m.stock.naver.com) 발굴 · LLM Top 5 매력도 분석 + Telegram 자동 보고서 · KST 09:00/15:00 watcher 자동 모니터링 대화 연속성 (v2.2.150~157): · [PRIOR TURN CONCLUSION] block 으로 직전 결론 anchor · thin follow-up 분류 → boilerplate 헤더 suppression · slash 명령 결과 chatHistory mirror (capture wrapper) · echo/parrot 금지 system prompt rule 기타: /stocks 슬래시 자동완성 dropdown UI, Naver JSON API 전환 (cheerio 제거) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -327,10 +327,18 @@ describe('AgentEngine — single-pass routing', () => {
|
||||
expect(AgentEngine.isObviouslySimple('hello world')).toBe(true);
|
||||
});
|
||||
|
||||
test('isObviouslySimple: 분석/리서치 키워드 포함 → false', () => {
|
||||
expect(AgentEngine.isObviouslySimple('이거 분석해줘')).toBe(false);
|
||||
expect(AgentEngine.isObviouslySimple('보고서 작성 부탁')).toBe(false);
|
||||
expect(AgentEngine.isObviouslySimple('아키텍처 설계 좀')).toBe(false);
|
||||
test('isObviouslySimple: 짧은 키워드 입력은 fast-path 허용 (v2 완화)', () => {
|
||||
// 80자 미만 + 키워드는 fast-path 통과 — "이거 분석해줘" 같은 짧은 요청에 chunked
|
||||
// 과잉 적용하던 옛 룰 완화.
|
||||
expect(AgentEngine.isObviouslySimple('이거 분석해줘')).toBe(true);
|
||||
expect(AgentEngine.isObviouslySimple('보고서 작성 부탁')).toBe(true);
|
||||
expect(AgentEngine.isObviouslySimple('아키텍처 설계 좀')).toBe(true);
|
||||
});
|
||||
|
||||
test('isObviouslySimple: 길고 키워드 포함은 chunked (≥80자 + 키워드)', () => {
|
||||
const longWithKw = '이 프로젝트의 전체 아키텍처를 종합적으로 분석해서 핵심 개선점을 보고서로 정리하고, 코드 베이스 전반을 꼼꼼히 살펴본 뒤 시니어 엔지니어 관점의 리뷰와 개선 제안까지 부탁드립니다.';
|
||||
expect(longWithKw.length).toBeGreaterThanOrEqual(80); // sanity
|
||||
expect(AgentEngine.isObviouslySimple(longWithKw)).toBe(false);
|
||||
});
|
||||
|
||||
test('isObviouslySimple: 본문 첨부 흔적 (코드 펜스 / 빈줄 다수) → false', () => {
|
||||
@@ -338,8 +346,8 @@ describe('AgentEngine — single-pass routing', () => {
|
||||
expect(AgentEngine.isObviouslySimple('첫 줄\n\n\n둘째 줄')).toBe(false);
|
||||
});
|
||||
|
||||
test('isObviouslySimple: 길이 200자 이상 → false', () => {
|
||||
const long = '가나다라마바사아자차카타파하'.repeat(20);
|
||||
test('isObviouslySimple: 길이 400자 이상은 키워드 무관하게 chunked', () => {
|
||||
const long = '가나다라마바사아자차카타파하'.repeat(40);
|
||||
expect(AgentEngine.isObviouslySimple(long)).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* MockLLMClient — IAIService 의 Mock 구현체.
|
||||
*
|
||||
* 의도: 회사 모드 dispatcher / ChunkedWriter / ceoPlanner 등 LLM 을 호출하는 코드
|
||||
* 경로를 *CI 환경에서도 테스트* 가능하게. 실제 Ollama / LM Studio 없이도 응답을
|
||||
* 미리 정의하거나 동적으로 생성 가능.
|
||||
*
|
||||
* 사용 예:
|
||||
* const ai = new MockLLMClient();
|
||||
* ai.setNextResponse('plan generated');
|
||||
* const result = await ai.chat({ user: 'do this' });
|
||||
*
|
||||
* // 또는 동적 응답:
|
||||
* ai.setResponder((req) => req.user.includes('analyze') ? 'analysis...' : 'ok');
|
||||
*
|
||||
* // 호출 이력 검증:
|
||||
* expect(ai.calls).toHaveLength(2);
|
||||
* expect(ai.calls[0].user).toBe('do this');
|
||||
*/
|
||||
import type {
|
||||
IAIService,
|
||||
AIChatRequest,
|
||||
AIChatResult,
|
||||
} from '../../src/core/services';
|
||||
|
||||
export interface RecordedCall {
|
||||
user: string;
|
||||
system?: string;
|
||||
model?: string;
|
||||
timeoutMs?: number;
|
||||
signalAborted?: boolean;
|
||||
}
|
||||
|
||||
type Responder = (req: AIChatRequest) => string | { content: string; empty?: boolean };
|
||||
|
||||
export class MockLLMClient implements IAIService {
|
||||
/** 모든 chat / call 호출의 입력 인자가 시간 순서로 누적. */
|
||||
public readonly calls: RecordedCall[] = [];
|
||||
|
||||
/** FIFO 큐 — setNextResponse 로 push, chat 호출마다 shift. */
|
||||
private readonly queued: string[] = [];
|
||||
|
||||
/** 큐가 비었을 때 사용할 fallback. setResponder 로 정의 가능. */
|
||||
private responder: Responder | null = null;
|
||||
|
||||
/** queued / responder 모두 없으면 이 값을 그대로. */
|
||||
private defaultResponse = 'mock response — set via setNextResponse / setResponder';
|
||||
|
||||
/**
|
||||
* 다음 호출(들) 에 사용할 응답을 FIFO 큐에 push.
|
||||
* 여러 번 push 하면 순차적으로 소비.
|
||||
*/
|
||||
setNextResponse(text: string): void {
|
||||
this.queued.push(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* 모든 호출에 대해 동적으로 응답 생성. setNextResponse 큐가 우선.
|
||||
*/
|
||||
setResponder(fn: Responder): void {
|
||||
this.responder = fn;
|
||||
}
|
||||
|
||||
/** 모든 큐 / responder / 이력 초기화. test setup 사이에 reset 용. */
|
||||
reset(): void {
|
||||
this.calls.length = 0;
|
||||
this.queued.length = 0;
|
||||
this.responder = null;
|
||||
}
|
||||
|
||||
async call(prompt: string): Promise<string> {
|
||||
const result = await this.chat({ user: prompt });
|
||||
return result.content;
|
||||
}
|
||||
|
||||
async chat(req: AIChatRequest): Promise<AIChatResult> {
|
||||
this.calls.push({
|
||||
user: req.user,
|
||||
system: req.system,
|
||||
model: req.model,
|
||||
timeoutMs: req.timeoutMs,
|
||||
signalAborted: req.signal?.aborted,
|
||||
});
|
||||
// signal 이 이미 aborted 면 AbortError 던짐 — 실제 fetch 동작 모방.
|
||||
if (req.signal?.aborted) {
|
||||
const err = new Error('AbortError');
|
||||
err.name = 'AbortError';
|
||||
throw err;
|
||||
}
|
||||
let content: string;
|
||||
let empty = false;
|
||||
if (this.queued.length > 0) {
|
||||
content = this.queued.shift()!;
|
||||
} else if (this.responder) {
|
||||
const out = this.responder(req);
|
||||
if (typeof out === 'string') {
|
||||
content = out;
|
||||
} else {
|
||||
content = out.content;
|
||||
empty = !!out.empty;
|
||||
}
|
||||
} else {
|
||||
content = this.defaultResponse;
|
||||
}
|
||||
return {
|
||||
content,
|
||||
engine: 'lmstudio',
|
||||
model: req.model || 'mock-model',
|
||||
empty: empty || !content,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* MockLLMClient 자체의 sanity test.
|
||||
*
|
||||
* 이게 통과하면 dispatcher / ceoPlanner / ChunkedWriter 등 IAIService 를 받는
|
||||
* 코드가 *실제 LLM 없이* 단위 / integration 테스트 가능.
|
||||
*
|
||||
* 향후 dispatcher 의 multi-stage flow 같은 큰 integration 테스트는 이 mock 을
|
||||
* 주입해서 실제 fetch 없이 검증.
|
||||
*/
|
||||
import { MockLLMClient } from '../helpers/mockLLMClient';
|
||||
|
||||
describe('MockLLMClient', () => {
|
||||
test('큐된 응답을 순차적으로 소비한다', async () => {
|
||||
const ai = new MockLLMClient();
|
||||
ai.setNextResponse('first');
|
||||
ai.setNextResponse('second');
|
||||
|
||||
const r1 = await ai.chat({ user: 'a' });
|
||||
const r2 = await ai.chat({ user: 'b' });
|
||||
|
||||
expect(r1.content).toBe('first');
|
||||
expect(r2.content).toBe('second');
|
||||
expect(ai.calls).toHaveLength(2);
|
||||
expect(ai.calls[0].user).toBe('a');
|
||||
expect(ai.calls[1].user).toBe('b');
|
||||
});
|
||||
|
||||
test('큐가 비면 responder 함수로 동적 응답', async () => {
|
||||
const ai = new MockLLMClient();
|
||||
ai.setResponder((req) =>
|
||||
req.user.includes('summary') ? '요약 결과' : '일반 답변',
|
||||
);
|
||||
|
||||
const r1 = await ai.chat({ user: 'give me summary' });
|
||||
const r2 = await ai.chat({ user: 'hello' });
|
||||
|
||||
expect(r1.content).toBe('요약 결과');
|
||||
expect(r2.content).toBe('일반 답변');
|
||||
});
|
||||
|
||||
test('큐가 responder 보다 우선', async () => {
|
||||
const ai = new MockLLMClient();
|
||||
ai.setResponder(() => 'from responder');
|
||||
ai.setNextResponse('from queue');
|
||||
|
||||
const r1 = await ai.chat({ user: 'x' });
|
||||
const r2 = await ai.chat({ user: 'y' });
|
||||
|
||||
expect(r1.content).toBe('from queue');
|
||||
expect(r2.content).toBe('from responder');
|
||||
});
|
||||
|
||||
test('signal 이 이미 abort 된 상태면 AbortError 던진다', async () => {
|
||||
const ai = new MockLLMClient();
|
||||
const ctrl = new AbortController();
|
||||
ctrl.abort();
|
||||
|
||||
await expect(ai.chat({ user: 'x', signal: ctrl.signal })).rejects.toThrow(/AbortError/);
|
||||
// 호출은 기록됨 (signalAborted: true).
|
||||
expect(ai.calls).toHaveLength(1);
|
||||
expect(ai.calls[0].signalAborted).toBe(true);
|
||||
});
|
||||
|
||||
test('legacy call(prompt) 도 동작', async () => {
|
||||
const ai = new MockLLMClient();
|
||||
ai.setNextResponse('plain');
|
||||
|
||||
const result = await ai.call('hi');
|
||||
|
||||
expect(result).toBe('plain');
|
||||
expect(ai.calls).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('reset() 으로 상태 초기화', async () => {
|
||||
const ai = new MockLLMClient();
|
||||
ai.setNextResponse('x');
|
||||
await ai.chat({ user: 'a' });
|
||||
|
||||
ai.reset();
|
||||
|
||||
expect(ai.calls).toHaveLength(0);
|
||||
// 큐도 비었으니 기본 fallback 응답.
|
||||
const r = await ai.chat({ user: 'b' });
|
||||
expect(r.content).toContain('mock response');
|
||||
});
|
||||
});
|
||||
@@ -2,6 +2,34 @@ import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { AgentExecutor } from '../src/agent';
|
||||
import { buildRequestHistory } from '../src/lib/contextBuilders/historyTransform';
|
||||
import { isThinkingPartnerRequest, isCasualConversationPrompt } from '../src/lib/contextBuilders/promptDetection';
|
||||
import { extractPriorityPreviewFiles } from '../src/lib/contextBuilders/projectEvidence';
|
||||
import { buildJarvisProjectBriefContext } from '../src/lib/contextBuilders/jarvisProjectBrief';
|
||||
import {
|
||||
shouldPreflightLocalProjectPath,
|
||||
classifyLocalProjectIntent,
|
||||
isProjectKnowledgeCreationRequest,
|
||||
isProjectReviewEvaluationRequest,
|
||||
buildLocalProjectIntentGuidance,
|
||||
buildAstraStanceContext,
|
||||
} from '../src/lib/contextBuilders/localProjectIntent';
|
||||
import {
|
||||
buildProjectKnowledgeFallbackAnswer,
|
||||
writeProjectKnowledgeRecord,
|
||||
} from '../src/lib/contextBuilders/projectKnowledge';
|
||||
import {
|
||||
inspectLocalProjectPath,
|
||||
enforceLocalPathReviewAnswer,
|
||||
} from '../src/lib/contextBuilders/localProjectPath';
|
||||
import {
|
||||
isBlockingProjectKnowledgeAnswer,
|
||||
buildRecentProjectKnowledgeContext,
|
||||
ensureRecentProjectKnowledgeEvidence,
|
||||
ensureLocalProjectPathEvidence,
|
||||
} from '../src/lib/contextBuilders/recentProjectKnowledge';
|
||||
import { isAstraModeArchitectureQuestion, buildAstraModeArchitectureContext } from '../src/lib/contextBuilders/astraModeArchitecture';
|
||||
import { shouldUseMultiAgentWorkflow } from '../src/lib/contextBuilders/multiAgentRouting';
|
||||
|
||||
function stateStore() {
|
||||
const store = new Map<string, any>();
|
||||
@@ -35,7 +63,7 @@ describe('local project path preflight', () => {
|
||||
};
|
||||
const agent = new AgentExecutor(context) as any;
|
||||
|
||||
const inspected = agent.inspectLocalProjectPath(root, root);
|
||||
const inspected = inspectLocalProjectPath(root, root);
|
||||
|
||||
expect(inspected).toContain('Access: succeeded');
|
||||
expect(inspected).toContain('Priority file previews');
|
||||
@@ -52,7 +80,7 @@ describe('local project path preflight', () => {
|
||||
};
|
||||
const agent = new AgentExecutor(context) as any;
|
||||
const answer = '코드를 업로드해 주시면 검토하겠습니다.';
|
||||
const fixed = agent.enforceLocalPathReviewAnswer(answer, 'Access: succeeded\nPriority file previews:\n### package.json');
|
||||
const fixed = enforceLocalPathReviewAnswer(answer, 'Access: succeeded\nPriority file previews:\n### package.json');
|
||||
|
||||
expect(fixed).toContain('제공된 로컬 프로젝트 경로에는 접근할 수 있고');
|
||||
expect(fixed).not.toContain('코드를 업로드해 주시면');
|
||||
@@ -67,7 +95,7 @@ describe('local project path preflight', () => {
|
||||
const agent = new AgentExecutor(context) as any;
|
||||
const prompt = '그러면 지금 /Volumes/Data/project/Antigravity/ConnectAI 이 프로젝트에 대한 지식을 만들면 되는거 아니야?';
|
||||
|
||||
expect(agent.shouldPreflightLocalProjectPath(prompt)).toBe(true);
|
||||
expect(shouldPreflightLocalProjectPath(prompt)).toBe(true);
|
||||
});
|
||||
|
||||
it('does not mistake a knowledge collection tool review for project knowledge creation', () => {
|
||||
@@ -79,9 +107,9 @@ describe('local project path preflight', () => {
|
||||
const agent = new AgentExecutor(context) as any;
|
||||
const prompt = '/Volumes/Data/project/Antigravity/Datacollector_MAC 이 프로젝트는 재가 지식을 수집하는데 사용하는 프로그램이야. 이 프로그램을 너가 코드리뷰를 하고 이 프로그램에 대한 너의 평가를 듣고 싶어. 장점과 단점, 앞으로의 확장성은 어떻게 잡아야할지.';
|
||||
|
||||
expect(agent.shouldPreflightLocalProjectPath(prompt)).toBe(true);
|
||||
expect(agent.isProjectReviewEvaluationRequest(prompt)).toBe(true);
|
||||
expect(agent.isProjectKnowledgeCreationRequest(prompt)).toBe(false);
|
||||
expect(shouldPreflightLocalProjectPath(prompt)).toBe(true);
|
||||
expect(isProjectReviewEvaluationRequest(prompt)).toBe(true);
|
||||
expect(isProjectKnowledgeCreationRequest(prompt)).toBe(false);
|
||||
});
|
||||
|
||||
it('classifies local project intent by requested task instead of subject words', () => {
|
||||
@@ -93,11 +121,11 @@ describe('local project path preflight', () => {
|
||||
const agent = new AgentExecutor(context) as any;
|
||||
const projectPath = '/Volumes/Data/project/Antigravity/Datacollector_MAC';
|
||||
|
||||
expect(agent.classifyLocalProjectIntent(`${projectPath} 지식 수집용 앱인데 한번 봐줘. 앞으로 확장성은?`)).toBe('review-evaluation');
|
||||
expect(agent.classifyLocalProjectIntent(`${projectPath} 데이터 수집 프로그램이야. 장단점과 리스크 평가해줘.`)).toBe('review-evaluation');
|
||||
expect(agent.classifyLocalProjectIntent(`${projectPath} 이 프로젝트에 대한 지식을 만들어줘.`)).toBe('knowledge-creation');
|
||||
expect(agent.classifyLocalProjectIntent(`${projectPath} 이 프로젝트 README와 사용 가이드를 문서로 정리해줘.`)).toBe('documentation');
|
||||
expect(agent.classifyLocalProjectIntent(`${projectPath} 이 프로젝트의 아키텍처 방향은 어떻게 생각해?`)).toBe('thinking');
|
||||
expect(classifyLocalProjectIntent(`${projectPath} 지식 수집용 앱인데 한번 봐줘. 앞으로 확장성은?`)).toBe('review-evaluation');
|
||||
expect(classifyLocalProjectIntent(`${projectPath} 데이터 수집 프로그램이야. 장단점과 리스크 평가해줘.`)).toBe('review-evaluation');
|
||||
expect(classifyLocalProjectIntent(`${projectPath} 이 프로젝트에 대한 지식을 만들어줘.`)).toBe('knowledge-creation');
|
||||
expect(classifyLocalProjectIntent(`${projectPath} 이 프로젝트 README와 사용 가이드를 문서로 정리해줘.`)).toBe('documentation');
|
||||
expect(classifyLocalProjectIntent(`${projectPath} 이 프로젝트의 아키텍처 방향은 어떻게 생각해?`)).toBe('thinking');
|
||||
});
|
||||
|
||||
it('adds deep review lenses for local project review intent', () => {
|
||||
@@ -107,7 +135,7 @@ describe('local project path preflight', () => {
|
||||
globalState: stateStore()
|
||||
};
|
||||
const agent = new AgentExecutor(context) as any;
|
||||
const guidance = agent.buildLocalProjectIntentGuidance('review-evaluation');
|
||||
const guidance = buildLocalProjectIntentGuidance('review-evaluation');
|
||||
|
||||
expect(guidance).toContain('Intent operating contract — Code Review');
|
||||
// v2.2.64: review labels switched from markdown headers ("## 한 줄 판단") to plain-text
|
||||
@@ -132,7 +160,7 @@ describe('local project path preflight', () => {
|
||||
'Access: succeeded'
|
||||
].join('\n');
|
||||
|
||||
const stance = agent.buildAstraStanceContext(prompt, localPathContext);
|
||||
const stance = buildAstraStanceContext(prompt, localPathContext);
|
||||
|
||||
expect(stance).toContain('[ASTRA STANCE LAYER]');
|
||||
expect(stance).toContain('not a template');
|
||||
@@ -153,8 +181,8 @@ describe('local project path preflight', () => {
|
||||
const agent = new AgentExecutor(context) as any;
|
||||
const prompt = '프로젝트 경로는 /Volumes/Data/project/Antigravity/ConnectAI 이야. 그럼 이 프로젝트에 대한 설계, 아키텍처는 어떤거 같아?';
|
||||
|
||||
expect(agent.shouldPreflightLocalProjectPath(prompt)).toBe(true);
|
||||
expect(agent.isThinkingPartnerRequest(prompt)).toBe(true);
|
||||
expect(shouldPreflightLocalProjectPath(prompt)).toBe(true);
|
||||
expect(isThinkingPartnerRequest(prompt)).toBe(true);
|
||||
});
|
||||
|
||||
it('classifies short greetings as casual conversation so RAG can be skipped', () => {
|
||||
@@ -165,22 +193,22 @@ describe('local project path preflight', () => {
|
||||
};
|
||||
const agent = new AgentExecutor(context) as any;
|
||||
|
||||
expect(agent.isCasualConversationPrompt('안녕')).toBe(true);
|
||||
expect(agent.isCasualConversationPrompt('hello!')).toBe(true);
|
||||
expect(agent.isCasualConversationPrompt('안녕, 이 프로젝트 구조 분석해줘')).toBe(false);
|
||||
expect(isCasualConversationPrompt('안녕')).toBe(true);
|
||||
expect(isCasualConversationPrompt('hello!')).toBe(true);
|
||||
expect(isCasualConversationPrompt('안녕, 이 프로젝트 구조 분석해줘')).toBe(false);
|
||||
// expanded coverage: affirmations / negations / thanks / laughs (still small talk)
|
||||
expect(agent.isCasualConversationPrompt('ㅇㅇ')).toBe(true);
|
||||
expect(agent.isCasualConversationPrompt('알겠어')).toBe(true);
|
||||
expect(agent.isCasualConversationPrompt('아니')).toBe(true);
|
||||
expect(agent.isCasualConversationPrompt('고마워!!')).toBe(true);
|
||||
expect(agent.isCasualConversationPrompt('ㅋㅋㅋ')).toBe(true);
|
||||
expect(agent.isCasualConversationPrompt('hahaha')).toBe(true);
|
||||
expect(agent.isCasualConversationPrompt('Thanks.')).toBe(true);
|
||||
expect(isCasualConversationPrompt('ㅇㅇ')).toBe(true);
|
||||
expect(isCasualConversationPrompt('알겠어')).toBe(true);
|
||||
expect(isCasualConversationPrompt('아니')).toBe(true);
|
||||
expect(isCasualConversationPrompt('고마워!!')).toBe(true);
|
||||
expect(isCasualConversationPrompt('ㅋㅋㅋ')).toBe(true);
|
||||
expect(isCasualConversationPrompt('hahaha')).toBe(true);
|
||||
expect(isCasualConversationPrompt('Thanks.')).toBe(true);
|
||||
// must NOT be treated as casual — these need memory / work, not a greeting reply
|
||||
expect(agent.isCasualConversationPrompt('기억해?')).toBe(false);
|
||||
expect(agent.isCasualConversationPrompt('방금 말한 거 이어서 해줘')).toBe(false);
|
||||
expect(agent.isCasualConversationPrompt('이 함수 고쳐줘')).toBe(false);
|
||||
expect(agent.isCasualConversationPrompt('')).toBe(false);
|
||||
expect(isCasualConversationPrompt('기억해?')).toBe(false);
|
||||
expect(isCasualConversationPrompt('방금 말한 거 이어서 해줘')).toBe(false);
|
||||
expect(isCasualConversationPrompt('이 함수 고쳐줘')).toBe(false);
|
||||
expect(isCasualConversationPrompt('')).toBe(false);
|
||||
});
|
||||
|
||||
it('adds concrete Astra mode architecture context for Guard and MA design questions', () => {
|
||||
@@ -192,8 +220,8 @@ describe('local project path preflight', () => {
|
||||
const agent = new AgentExecutor(context) as any;
|
||||
const prompt = '지금 우리는 guard 모드가 있고 MA 모드가 있는데 굳이 이렇게 모드를 분리해서 사용하는게 좋을까?';
|
||||
|
||||
expect(agent.isAstraModeArchitectureQuestion(prompt)).toBe(true);
|
||||
const modeContext = agent.buildAstraModeArchitectureContext(prompt);
|
||||
expect(isAstraModeArchitectureQuestion(prompt)).toBe(true);
|
||||
const modeContext = buildAstraModeArchitectureContext(prompt);
|
||||
expect(modeContext).toContain('Confirmed implementation facts');
|
||||
expect(modeContext).toContain('Guard should be an always-on policy/context layer');
|
||||
expect(modeContext).toContain('MA should be an optional execution strategy');
|
||||
@@ -215,9 +243,9 @@ describe('local project path preflight', () => {
|
||||
};
|
||||
const agent = new AgentExecutor(context) as any;
|
||||
|
||||
expect(agent.shouldUseMultiAgentWorkflow('시장 조사 기반으로 긴 보고서를 작성해줘', false)).toBe(true);
|
||||
expect(agent.shouldUseMultiAgentWorkflow('프로젝트 경로는 /Volumes/Data/project/Antigravity/ConnectAI 이야. 아키텍처 봐줘', false)).toBe(false);
|
||||
expect(agent.shouldUseMultiAgentWorkflow('guard 모드와 MA 모드를 분리하는게 좋을까?', true)).toBe(false);
|
||||
expect(shouldUseMultiAgentWorkflow('시장 조사 기반으로 긴 보고서를 작성해줘', false)).toBe(true);
|
||||
expect(shouldUseMultiAgentWorkflow('프로젝트 경로는 /Volumes/Data/project/Antigravity/ConnectAI 이야. 아키텍처 봐줘', false)).toBe(false);
|
||||
expect(shouldUseMultiAgentWorkflow('guard 모드와 MA 모드를 분리하는게 좋을까?', true)).toBe(false);
|
||||
});
|
||||
|
||||
it('removes file-structure requests when knowledge creation path access already succeeded', () => {
|
||||
@@ -228,7 +256,7 @@ describe('local project path preflight', () => {
|
||||
};
|
||||
const agent = new AgentExecutor(context) as any;
|
||||
const answer = '프로젝트에 대한 지식을 만들고 싶다면, 먼저 해당 프로젝트의 핵심 파일이나 구조를 저에게 제공해 주셔야 합니다. 실제 구현 근거 없이는 유용한 지식을 만들 수 없습니다.';
|
||||
const fixed = agent.enforceLocalPathReviewAnswer(answer, 'Access: succeeded\nPriority file previews:\n### package.json');
|
||||
const fixed = enforceLocalPathReviewAnswer(answer, 'Access: succeeded\nPriority file previews:\n### package.json');
|
||||
|
||||
expect(fixed).toContain('액션 태그를 통해 스스로 필요한 코드를 열어보겠습니다');
|
||||
expect(fixed).not.toContain('핵심 파일이나 구조를 저에게 제공');
|
||||
@@ -257,8 +285,8 @@ describe('local project path preflight', () => {
|
||||
'export class AgentExecutor {}'
|
||||
].join('\n');
|
||||
|
||||
expect(agent.isBlockingProjectKnowledgeAnswer(answer)).toBe(true);
|
||||
const fallback = agent.buildProjectKnowledgeFallbackAnswer(localPathContext);
|
||||
expect(isBlockingProjectKnowledgeAnswer(answer)).toBe(true);
|
||||
const fallback = buildProjectKnowledgeFallbackAnswer(localPathContext);
|
||||
expect(fallback).toContain('추가 질문으로 멈출 필요 없이');
|
||||
expect(fallback).toContain('Astra Project Knowledge Overview');
|
||||
expect(fallback).not.toContain('어떤 기능 영역을 가장 먼저');
|
||||
@@ -277,7 +305,7 @@ describe('local project path preflight', () => {
|
||||
const agent = new AgentExecutor(context) as any;
|
||||
const answer = 'Record Path Check: 요청하신 지식 생성 작업은 파일 목록 검토를 통해 완료되었으며, 별도의 파일 기록이 생성되지 않았습니다.';
|
||||
|
||||
expect(agent.isBlockingProjectKnowledgeAnswer(answer)).toBe(true);
|
||||
expect(isBlockingProjectKnowledgeAnswer(answer)).toBe(true);
|
||||
});
|
||||
|
||||
it('extracts only preview file markers, not markdown headings inside previews', () => {
|
||||
@@ -295,7 +323,7 @@ describe('local project path preflight', () => {
|
||||
'export class AgentExecutor {}'
|
||||
].join('\n');
|
||||
|
||||
expect(agent.extractPriorityPreviewFiles(localPathContext)).toEqual(['README.md', 'src/agent.ts']);
|
||||
expect(extractPriorityPreviewFiles(localPathContext)).toEqual(['README.md', 'src/agent.ts']);
|
||||
});
|
||||
|
||||
it('writes a project knowledge record for accessible local project context', () => {
|
||||
@@ -319,7 +347,7 @@ describe('local project path preflight', () => {
|
||||
'export class CombatSystem {}'
|
||||
].join('\n');
|
||||
|
||||
const record = agent.writeProjectKnowledgeRecord(localPathContext);
|
||||
const record = writeProjectKnowledgeRecord(localPathContext)!;
|
||||
|
||||
expect(record?.filePath).toBeTruthy();
|
||||
expect(fs.existsSync(record.filePath)).toBe(true);
|
||||
@@ -351,7 +379,7 @@ describe('local project path preflight', () => {
|
||||
'utf8'
|
||||
);
|
||||
|
||||
const followup = agent.buildRecentProjectKnowledgeContext('이젠 아키텍처에 대한 조사도 해주면 좋을 것 같아.', root);
|
||||
const followup = buildRecentProjectKnowledgeContext('이젠 아키텍처에 대한 조사도 해주면 좋을 것 같아.', root, []);
|
||||
|
||||
expect(followup).toContain('[RECENT LOCAL PROJECT KNOWLEDGE]');
|
||||
expect(followup).toContain(recordPath);
|
||||
@@ -383,7 +411,7 @@ describe('local project path preflight', () => {
|
||||
}
|
||||
];
|
||||
|
||||
const requestHistory = agent.buildRequestHistory(history);
|
||||
const requestHistory = buildRequestHistory(history as any);
|
||||
|
||||
expect(requestHistory[1].content).toContain('ConnectAI 프로젝트 지식을 만들었습니다');
|
||||
expect(requestHistory[1].content).not.toContain('Datacollector');
|
||||
@@ -411,7 +439,7 @@ describe('local project path preflight', () => {
|
||||
].join('\n');
|
||||
const answer = '## 간단 요약\nConnectAI 아키텍처는 실행 흐름 분리를 중심으로 구성됩니다.';
|
||||
|
||||
const fixed = agent.ensureRecentProjectKnowledgeEvidence(answer, recentContext);
|
||||
const fixed = ensureRecentProjectKnowledgeEvidence(answer, recentContext);
|
||||
|
||||
expect(fixed).toContain('## 근거');
|
||||
expect(fixed).toContain(recordPath);
|
||||
@@ -445,7 +473,7 @@ describe('local project path preflight', () => {
|
||||
].join('\n');
|
||||
const answer = '## 간단 요약\nConnectAI 아키텍처는 실행 흐름 분리를 중심으로 구성됩니다.';
|
||||
|
||||
const fixed = agent.ensureLocalProjectPathEvidence(answer, localPathContext);
|
||||
const fixed = ensureLocalProjectPathEvidence(answer, localPathContext);
|
||||
|
||||
expect(fixed).toContain('## 근거');
|
||||
expect(fixed).toContain('/Volumes/Data/project/Antigravity/ConnectAI');
|
||||
@@ -476,7 +504,7 @@ describe('local project path preflight', () => {
|
||||
'export class AgentExecutor {}'
|
||||
].join('\n');
|
||||
|
||||
const brief = agent.buildJarvisProjectBriefContext(
|
||||
const brief = buildJarvisProjectBriefContext(
|
||||
'그럼 이 프로젝트에 대한 설계, 아키텍처는 어떤거 같아?',
|
||||
localPathContext,
|
||||
''
|
||||
|
||||
Reference in New Issue
Block a user