feat: Self-Evolving Digital Employee OS P0~P6 + 캘린더 충돌 게이트
신뢰성 코어 (P1~P2): - Requirement Graph: 업무 유형(회의록/시장조사/업무조사/일정) 필수 요소 주입 + 커버리지 hook - Confidence Engine(0~100 결정론적) / Escalation Engine(검토 요청) / Epistemic Guard(모름·추정·확실 3분류) - Provenance: citationTrace 에 출처 수정일·오래됨 경고 - Critic Loop: 문제 신호 turn 만 LLM 검수 1회 + 보완 카드 성장 루프 (P3): - Gap Detector(Requirement-Knowledge) / Need Engine(30/25/20/15/10 공식) / Knowledge Inventory - Learning Queue(proposed 전용 병합 — 승인은 사람만) / Decision Journal / Reflection 기록 - 반복 누락 요소(3회+)는 다음 turn 체크리스트에 자동 강조 (T5 루프) 지식 운영 (P4) + 기억 (P5) + 학습 실행 (P6): - Knowledge Validation + Belief Revision(중복 reject·충돌 시 update/add 권고) - Knowledge Decay(분야별 반감기 감사) / Knowledge Debt(blocked x impact) - Organizational Memory(.astra/organization.md 상시 주입) - Research Agent(approved 큐 -> 조사 브리프+추정 라벨 초안+Validation 게이트 -> proposals/) - Skill Score(전/후반 추세) + Success Pattern DB(전요소충족+확신도90+ 자동 적재) 병렬 트랙: - 캘린더 충돌 게이트: conflictCheck + 구조화 이벤트 캐시 + create_calendar_event 차단(force 는 사용자 승인 후) - Task Eval Harness: 회의록 골든셋 자동 채점 명령 + 성장 리포트/학습 큐/노후 점검 명령 신규 모듈 17종(src/intelligence/), VS Code 명령 5종, 설정 11종, 테스트 +89건(전체 508 통과). 설계 문서: docs/SELF_EVOLVING_OS_MASTER_PLAN.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -196,6 +196,15 @@ export async function refreshCalendarCache(context: vscode.ExtensionContext): Pr
|
||||
try {
|
||||
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
|
||||
fs.writeFileSync(cachePath, md, 'utf8');
|
||||
// 구조화 JSON 캐시 — 충돌 감지(conflictCheck)가 사용. md 와 같은 시점·같은 범위.
|
||||
const structured = upcoming.map((e) => ({
|
||||
summary: e.summary,
|
||||
startIso: e.start.toISOString(),
|
||||
endIso: e.end ? e.end.toISOString() : undefined,
|
||||
allDay: e.allDay,
|
||||
location: e.location || undefined,
|
||||
}));
|
||||
fs.writeFileSync(_eventsJsonPath(cachePath), JSON.stringify(structured, null, 2), 'utf8');
|
||||
} catch (e: any) {
|
||||
return { ok: false, count: 0, error: `캐시 저장 실패: ${e?.message ?? String(e)}`, cachePath };
|
||||
}
|
||||
@@ -215,6 +224,27 @@ export function readCalendarCache(context: vscode.ExtensionContext): string {
|
||||
}
|
||||
}
|
||||
|
||||
function _eventsJsonPath(mdCachePath: string): string {
|
||||
return mdCachePath.replace(/\.md$/, '.json');
|
||||
}
|
||||
|
||||
/**
|
||||
* 구조화 이벤트 캐시 읽기 — 충돌 감지용. 캐시 없음/깨짐 → 빈 배열
|
||||
* (충돌 검사가 일정 생성을 막는 false-positive 를 내지 않도록 보수적).
|
||||
*/
|
||||
export function readCalendarEventsCache(context: vscode.ExtensionContext): Array<{
|
||||
summary: string; startIso: string; endIso?: string; allDay: boolean; location?: string;
|
||||
}> {
|
||||
try {
|
||||
const file = _eventsJsonPath(_cachePath(context));
|
||||
if (!fs.existsSync(file)) return [];
|
||||
const arr = JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||
return Array.isArray(arr) ? arr.filter((e: any) => e && typeof e.startIso === 'string') : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function _renderMarkdown(events: IcsEvent[], daysAhead: number, now: Date): string {
|
||||
const tsLabel = (d: Date, allDay: boolean) => {
|
||||
const yy = d.getFullYear(), mm = String(d.getMonth() + 1).padStart(2, '0'), dd = String(d.getDate()).padStart(2, '0');
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Schedule Conflict Check — 일정 생성 전 기존 일정과의 겹침 감지.
|
||||
*
|
||||
* Self-Evolving OS 마스터 플랜 병렬 트랙 6-2 + 6-3. Requirement Graph 의
|
||||
* 일정 필수 요소 "충돌 확인" 과 Constitution "승인 없는 외부 액션 금지" 의
|
||||
* 실행 계층:
|
||||
* - 에이전트가 <create_calendar_event> 로 일정을 만들기 *전에* ICS 캐시와
|
||||
* 비교해 겹침을 감지
|
||||
* - 충돌 시 생성을 *차단*하고 사용자 확인을 요청 (force="true" 명시 시에만 강행)
|
||||
*
|
||||
* 순수 모듈 — vscode/네트워크 의존 없음. 캐시 공급은 calendarCache, 차단 배선은
|
||||
* agent/actions/calendar.ts 담당.
|
||||
*/
|
||||
|
||||
export interface CachedCalEvent {
|
||||
summary: string;
|
||||
/** ISO 문자열 (toISOString 또는 로컬 'YYYY-MM-DDTHH:MM'). */
|
||||
startIso: string;
|
||||
/** 없으면 1시간으로 간주. */
|
||||
endIso?: string;
|
||||
allDay: boolean;
|
||||
location?: string;
|
||||
}
|
||||
|
||||
export interface CandidateEvent {
|
||||
startIso: string;
|
||||
endIso?: string;
|
||||
/** endIso 없을 때 사용. 기본 60분. */
|
||||
durationMinutes?: number;
|
||||
allDay?: boolean;
|
||||
}
|
||||
|
||||
const HOUR_MS = 3600000;
|
||||
const DAY_MS = 86400000;
|
||||
|
||||
function parseIso(iso: string): number | null {
|
||||
const t = Date.parse(iso);
|
||||
return isNaN(t) ? null : t;
|
||||
}
|
||||
|
||||
/** [start, end) 구간 계산. all-day 는 해당 날짜 00:00~다음날 00:00 (로컬). */
|
||||
function rangeOf(startIso: string, endIso: string | undefined, durationMinutes: number | undefined, allDay: boolean): [number, number] | null {
|
||||
const start = parseIso(startIso);
|
||||
if (start === null) return null;
|
||||
if (allDay) {
|
||||
const d = new Date(start);
|
||||
const dayStart = new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
|
||||
return [dayStart, dayStart + DAY_MS];
|
||||
}
|
||||
let end: number | null = endIso ? parseIso(endIso) : null;
|
||||
if (end === null) end = start + (durationMinutes && durationMinutes > 0 ? durationMinutes * 60000 : HOUR_MS);
|
||||
if (end <= start) end = start + HOUR_MS; // 역전 입력 방어
|
||||
return [start, end];
|
||||
}
|
||||
|
||||
/**
|
||||
* 후보 일정과 겹치는 기존 일정 반환. 파싱 불가능한 입력은 보수적으로 *충돌 없음*
|
||||
* 처리 (잘못된 날짜로 생성 자체가 실패할 것이므로 여기서 막지 않는다).
|
||||
*/
|
||||
export function findScheduleConflicts(existing: CachedCalEvent[], candidate: CandidateEvent): CachedCalEvent[] {
|
||||
const cand = rangeOf(candidate.startIso, candidate.endIso, candidate.durationMinutes, candidate.allDay === true);
|
||||
if (!cand) return [];
|
||||
const [cs, ce] = cand;
|
||||
const out: CachedCalEvent[] = [];
|
||||
for (const ev of existing || []) {
|
||||
const r = rangeOf(ev.startIso, ev.endIso, undefined, ev.allDay);
|
||||
if (!r) continue;
|
||||
const [es, ee] = r;
|
||||
if (cs < ee && es < ce) out.push(ev); // 구간 겹침
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 충돌 보고 텍스트 — 액션 리포트·에이전트 internal 메시지 공용. */
|
||||
export function formatConflictReport(conflicts: CachedCalEvent[]): string {
|
||||
const lines = conflicts.slice(0, 5).map((c) => {
|
||||
const when = c.allDay ? `${c.startIso.slice(0, 10)} (종일)` : c.startIso.replace('T', ' ').slice(0, 16);
|
||||
return `- ${c.summary} · ${when}${c.location ? ` · ${c.location}` : ''}`;
|
||||
});
|
||||
return `기존 일정과 겹칩니다:\n${lines.join('\n')}\n생성을 보류했습니다. 그래도 진행하려면 사용자 확인 후 force="true" 로 다시 시도하세요.`;
|
||||
}
|
||||
@@ -11,9 +11,17 @@ export {
|
||||
writeCalendarConfig,
|
||||
refreshCalendarCache,
|
||||
readCalendarCache,
|
||||
readCalendarEventsCache,
|
||||
RefreshResult,
|
||||
} from './calendarCache';
|
||||
|
||||
export {
|
||||
findScheduleConflicts,
|
||||
formatConflictReport,
|
||||
CachedCalEvent,
|
||||
CandidateEvent,
|
||||
} from './conflictCheck';
|
||||
|
||||
export {
|
||||
runOAuthLoopback,
|
||||
refreshAccessToken,
|
||||
|
||||
Reference in New Issue
Block a user