import * as fs from 'fs'; import type { ProjectProfile } from '../../features/projectChronicle'; import { formatArchitectureContextForPrompt } from '../../features/projectArchitecture'; /** * Architecture (chronicle 프로젝트 자동 attach) 관련 webview payload + prompt * context 빌더. webview send / fs write / scan 같은 *상태성* 부분은 provider 와 * `ArchitectureWatchManager` 가 계속 책임 — 이 모듈은 데이터 변환만. * * - buildArchitectureStatusPayload(profile, hasDoc, wasDetached) * → 'architectureStatus' 메시지의 active/inactive/hidden 분기 * - buildProjectArchitectureContext(profile) * → agent.ts 가 prompt 에 prepend 하는 architecture context string */ /** * Sidebar chip 의 3 가지 상태: * 1) active — auto-attach 켜져 있고 doc 존재. 칩에 "마지막 갱신 N분 전" 표시. * 2) inactive (canAttach) — workspace + project 는 있으나 doc 미생성 or detached. * 칩에 [Attach] 버튼 노출. `detached` 플래그로 "Activate" vs "Re-attach" 구분. * 3) hidden — profile null. 호출자가 `{ active: false }` 만 보내고 끝. * * 빌더는 (1)(2) 만 만들고, hidden 은 호출자가 직접 보낸다 (profile 이 없는데 * 빌더에 넘겨봤자 의미 없음). */ export function buildArchitectureStatusPayload(profile: ProjectProfile, hasDoc: boolean, wasDetached: boolean) { const fullyActive = hasDoc && !wasDetached; if (fullyActive) { return { type: 'architectureStatus' as const, value: { active: true, projectId: profile.projectId, projectName: profile.projectName, docPath: profile.architectureDocPath, lastUpdated: profile.architectureLastUpdated || '', autoUpdate: profile.architectureAutoUpdate !== false, }, }; } return { type: 'architectureStatus' as const, value: { active: false, canAttach: !!profile.projectRoot, projectId: profile.projectId, projectName: profile.projectName, // "never activated" 와 "detached" 구분 — chip 라벨 결정용. detached: wasDetached, }, }; } /** Workspace 도 profile 도 없는 hidden 상태 — chip 자체 숨김. */ export function buildArchitectureHiddenPayload() { return { type: 'architectureStatus' as const, value: { active: false as const }, }; } /** * Refresh / Attach 가 사전 가드에서 실패 (활성 project 없음 / projectRoot 없음). * webview 는 spinner 를 해제하고 사용자에게 안내 띄움. */ export function buildArchitectureRefreshFailedPayload(reason: string) { return { type: 'architectureRefreshFailed' as const, value: { reason }, }; } /** * Refresh 가 성공적으로 수행된 후 webview 에 보낼 결과. 3가지 숫자 (newly / * cached / deleted) 가 함께 가야 사용자가 "Refresh 가 실제로 뭘 했는가" 를 * 신뢰할 수 있다 — 그냥 timestamp 만 바뀐 경우와 분 단위로 큰 작업이 일어난 * 경우를 구분. */ export function buildArchitectureRefreshResultPayload(opts: { projectName: string; docPath: string; newlyAnalyzed: number; cached: number; deleted: number; durationMs: number; }) { return { type: 'architectureRefreshResult' as const, value: opts, }; } /** * Agent 의 prompt 에 prepend 되는 architecture context block. auto-attach 가 off * 거나 doc 이 없거나 디스크에서 사라졌으면 빈 문자열 (agent.ts 가 알아서 skip). */ export function buildProjectArchitectureContext(profile: ProjectProfile | null): string { if (!profile || profile.architectureAutoAttach === false || !profile.architectureDocPath) return ''; if (!fs.existsSync(profile.architectureDocPath)) return ''; return formatArchitectureContextForPrompt({ projectName: profile.projectName, docPath: profile.architectureDocPath, // project root 를 같이 넘겨 Source: 헤더가 workspace-relative 로 출력되게. projectRoot: profile.projectRoot, lastUpdated: profile.architectureLastUpdated, }); }