feat: 작업실 프로젝트별 개요 폴더 + readme.md 자동생성
- workshop/<프로젝트id>/개요/readme.md — 대시보드 데이터(개요/예산/부품/작업/메모)에서 자동 생성, saveWorkshop과 대시보드 data GET에서 내용 변화 때만 동기화(기존 프로젝트 백필 포함) - WorkshopProject.description 신설 + workshop_project set_description 액션 — 채팅 모델이 서술형 개요를 쓰면 readme 맨 위 개요 섹션으로 들어감 - formatProject/budgetSummary/fmtWon을 storage로 이동(도구 get과 readme가 같은 문서 소스) - 파일탭에는 개요 폴더 그룹으로 자동 표시(walkCaseFiles) Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,7 @@ export interface WorkshopPhase {
|
||||
export interface WorkshopProject {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string; // 서술형 프로젝트 개요(set_description 액션). 개요/readme.md의 "## 개요" 섹션
|
||||
parts: WorkshopPart[];
|
||||
phases: WorkshopPhase[];
|
||||
notes: string;
|
||||
@@ -150,6 +151,81 @@ export function saveWorkshop(workspaceRoot: string, data: WorkshopData): void {
|
||||
const tmp = `${p}.tmp`;
|
||||
fs.writeFileSync(tmp, JSON.stringify(data, null, 2), 'utf-8');
|
||||
fs.renameSync(tmp, p);
|
||||
syncProjectReadmes(workspaceRoot, data);
|
||||
}
|
||||
|
||||
// ── 프로젝트 readme(2026-09-23) ──────────────────────────────────────────────
|
||||
// 대시보드 데이터를 그대로 마크다운으로 내보낸다 — workshop-project.ts의 formatProject를
|
||||
// 여기로 옮긴 것(순환 import 방지: 도구가 storage를 import하므로 반대 방향은 안 됨).
|
||||
// 도구의 'get' 액션과 개요/readme.md 생성이 같은 문서를 쓴다.
|
||||
export function fmtWon(n: number): string {
|
||||
return `${Math.round(n).toLocaleString('ko-KR')}원`;
|
||||
}
|
||||
|
||||
export function budgetSummary(parts: WorkshopPart[]): string {
|
||||
const sum = (status: string) => parts.filter(p => p.status === status).reduce((s, p) => s + p.qty * p.unitPrice, 0);
|
||||
const review = sum('검토중'), ordered = sum('주문완료'), owned = sum('보유');
|
||||
return `검토중 ${fmtWon(review)} · 주문완료 ${fmtWon(ordered)} · 보유 ${fmtWon(owned)} · 총 예상비용 ${fmtWon(review + ordered + owned)}`;
|
||||
}
|
||||
|
||||
export function projectToReadme(project: WorkshopProject): string {
|
||||
const lines: string[] = [];
|
||||
lines.push(`# ${project.name}`);
|
||||
const desc = String(project.description || '').trim();
|
||||
if (desc) {
|
||||
lines.push('');
|
||||
lines.push('## 개요');
|
||||
lines.push(desc);
|
||||
}
|
||||
lines.push('');
|
||||
lines.push(`예산: ${budgetSummary(project.parts)}`);
|
||||
lines.push('');
|
||||
lines.push('## 부품');
|
||||
for (const p of project.parts) {
|
||||
lines.push(`- [${p.status}] ${p.name} × ${p.qty} = ${fmtWon(p.qty * p.unitPrice)}${p.memo ? ` (${p.memo})` : ''}`);
|
||||
for (const link of p.links || []) {
|
||||
lines.push(` - ${link.label}: ${link.url}`);
|
||||
}
|
||||
}
|
||||
lines.push('');
|
||||
lines.push('## 작업');
|
||||
for (const ph of project.phases) {
|
||||
lines.push(`### ${ph.name}`);
|
||||
for (const t of ph.tasks) {
|
||||
lines.push(`- [${t.done ? 'x' : ' '}] ${t.text}`);
|
||||
}
|
||||
}
|
||||
if (project.notes.trim()) {
|
||||
lines.push('');
|
||||
lines.push('## 메모');
|
||||
lines.push(project.notes);
|
||||
}
|
||||
lines.push('');
|
||||
lines.push('---');
|
||||
lines.push('*이 파일은 작업실 대시보드 데이터에서 자동 생성됨. 부품·작업·메모를 바꾸면 갱신되니 이 파일을 직접 고치지 말 것.*');
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
// 프로젝트당 workshop/<프로젝트id>/개요/readme.md 를 유지한다 — 파일탭에서 프로젝트
|
||||
// 설명 문서가 항상 존재하게. 내용이 바뀌었을 때만 쓴다(전수 saveWorkshop 경로라 값싸게).
|
||||
// 개별 프로젝트 실패(권한 등)는 나머지에 영향을 주지 않게 전부 삼킨다.
|
||||
export function syncProjectReadmes(workspaceRoot: string, data: WorkshopData): void {
|
||||
for (const project of data.projects) {
|
||||
try {
|
||||
const dir = path.join(workspaceRoot, 'workshop', project.id, '개요');
|
||||
const file = path.join(dir, 'readme.md');
|
||||
const md = projectToReadme(project);
|
||||
try {
|
||||
if (fs.readFileSync(file, 'utf-8') === md) continue;
|
||||
} catch {
|
||||
// 파일이 없으면 새로 쓴다
|
||||
}
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
fs.writeFileSync(file, md, 'utf-8');
|
||||
} catch {
|
||||
// 파생 문서 생성 실패는 원본 데이터 저장을 막아선 안 된다
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function genWorkshopId(prefix: string): string {
|
||||
|
||||
Reference in New Issue
Block a user