diff --git a/PATCHNOTES.md b/PATCHNOTES.md index 6788ed8..f798842 100644 --- a/PATCHNOTES.md +++ b/PATCHNOTES.md @@ -1,5 +1,26 @@ # Astra Patch Notes +## v2.2.253 (2026-06-17) +### ๐Ÿช“ /meet ์กฐ๊ฐ ์‹คํŒจ ์‹œ ์ ˆ๋ฐ˜ ๋ถ„ํ•  ์žฌ์‹œ๋„ (์•ฝํ•œ ๋ชจ๋ธ ์„ฑ๊ณต๋ฅ โ†‘) +- v2.2.252 ์˜ ์žฌ์‹œ๋„(๋ฐ˜๋ณต ์–ต์ œ ๊ฐ•ํ™”)์—๋„ ์กฐ๊ฐ์ด ๊ณ„์† ๋ถ•๊ดดํ•˜๋ฉด, ๊ทธ ์กฐ๊ฐ์„ **์ค„ ๊ฒฝ๊ณ„๋กœ ์ ˆ๋ฐ˜์”ฉ ์ชผ๊ฐœ ์žฌ๊ท€ ์žฌ์‹œ๋„**ํ•œ๋‹ค(12Kโ†’6Kโ†’3.5K). ์ž…๋ ฅ์ด ์ž‘์•„์งˆ์ˆ˜๋ก ์•ฝํ•œ ๋ชจ๋ธ์˜ ์ถœ๋ ฅ ๋ถ•๊ดด ํ™•๋ฅ ์ด ๋–จ์–ด์ง€๋ฏ€๋กœ, **๋ชจ๋ธ ๊ต์ฒด ์—†์ด๋„** ์ถ”์ถœ ์„ฑ๊ณต๋ฅ ์ด ์˜ค๋ฅธ๋‹ค. ์ตœ์†Œ ํฌ๊ธฐ(3.5K) ์ดํ•˜์ธ๋ฐ๋„ ์‹คํŒจํ•˜๋Š” ๊ตฌ๊ฐ„๋งŒ ๊ฑด๋„ˆ๋›ด๋‹ค. ([handlers.ts](src/features/datacollect/handlers.ts)) +- ์ง„ํ–‰ ๋กœ๊ทธ์— ๋ถ„ํ•  ์žฌ์‹œ๋„ ๊ณผ์ •์„ ๋…ธ์ถœ(`โ†ฉ๏ธŽ ์กฐ๊ฐ 1(12,000์ž) ์ถœ๋ ฅ ๋ถ•๊ดด โ†’ ์ ˆ๋ฐ˜์œผ๋กœ ์ชผ๊ฐœ ์žฌ์‹œ๋„`). +- โš ๏ธ ๊ทผ๋ณธ ์›์ธ์€ ๋ชจ๋ธ(`gemma-4-26b-a4b-it`, ํ™œ์„ฑ ~4B)์ด ๊ธด ํ•œ๊ตญ์–ด ์ฒ˜๋ฆฌ์— ์•ฝํ•œ ๊ฒƒ โ€” ๋ถ„ํ• ๋กœ ์™„ํ™”๋  ๋ฟ ์™„์น˜๋Š” **27B+๊ธ‰ ๋˜๋Š” ํ•œ๊ตญ์–ด ํŠนํ™” ๋ชจ๋ธ(EXAONE/Qwen ๋“ฑ)** ์ „ํ™˜ ๊ถŒ์žฅ. + +## v2.2.252 (2026-06-17) +### ๐Ÿ›ก๏ธ /meet ๋ชจ๋ธ ์ถœ๋ ฅ ๋ถ•๊ดด(degeneration) ๋Œ€์‘ + ํšŒ์˜๋ก ๊ฐ€์ด๋“œ v2 ๋ฐ˜์˜ +- **์ถœ๋ ฅ ๋ถ•๊ดด ๋ณต์›๋ ฅ**: ์•ฝํ•œ ๋กœ์ปฌ ๋ชจ๋ธ์ด ๊ธด ํ•œ๊ตญ์–ด ๋…น์ทจ๋ก์—์„œ ๋ฐ˜๋ณต ๋ฃจํ”„ยทํ† ํฐ ๊นจ์ง("ํ†ค์„ ํ†ค์„ ํ†ค์„โ€ฆ", ๊นจ์ง„ ์œ ๋‹ˆ์ฝ”๋“œ)์— ๋น ์ ธ LM ์„œ๋ฒ„๊ฐ€ `Failed to parse input`์„ ๋˜์ง€๋˜ ๋ฌธ์ œ. `callLmSynthesis`์— **์žฌ์‹œ๋„ ๋‚ด์žฅ**(์‹œ๋„๋งˆ๋‹ค `repeat_penalty`โ†‘ 1.1โ†’1.25โ†’1.4, `top_k`โ†“ 20โ†’15โ†’10์œผ๋กœ ๋ฐ˜๋ณต ์–ต์ œ ๊ฐ•ํ™”) + **degeneration ๊ฐ์ง€**(`looksDegenerate`) ์ถ”๊ฐ€. ๋ชจ๋“  datacollect LLM ํ˜ธ์ถœ์ด ํ˜œํƒ. ([llm.ts](src/features/datacollect/llm.ts)) +- **๋ถ€๋ถ„ ํšŒ์˜๋ก fallback**: ๊ธด ๋…น์ทจ 2๋‹จ๊ณ„ ํ•ฉ์„ฑ์—์„œ ํ•œ ์กฐ๊ฐ์ด ๋๋‚ด ์‹คํŒจํ•ด๋„ ์ „์ฒด๋ฅผ ์ค‘๋‹จํ•˜์ง€ ์•Š๊ณ  ํ•ด๋‹น ๊ตฌ๊ฐ„๋งŒ ํ‘œ์‹œ ํ›„ ๋‚˜๋จธ์ง€๋กœ **๋ถ€๋ถ„ ํšŒ์˜๋ก** ์ƒ์„ฑ. ์ „ ์กฐ๊ฐ ์‹คํŒจ ์‹œ์—๋งŒ ์ค‘๋‹จํ•˜๊ณ , ๋” ํฐ ๋ชจ๋ธ(27B+) ์‚ฌ์šฉ์„ ์•ˆ๋‚ด. ([handlers.ts](src/features/datacollect/handlers.ts)) +- **ํšŒ์˜๋ก ๊ฐ€์ด๋“œ v2**: ์„น์…˜ ์šฐ์„ ์ˆœ์œ„ ์žฌ์ •๋ ฌ(โ‘ ๊ฒฐ์ • โ‘ก์•ก์…˜ โ‘ข์˜คํ”ˆ์ด์Šˆ โ‘ฃ๋ฆฌ์Šคํฌ โ‘ค๋…ผ์˜), **๋…ผ์˜์‚ฌํ•ญ์„ ์ฃผ์ œ๋ณ„ bullet**๋กœ ๊ฐ„๊ฒฐํ™”, **์˜คํ”ˆ ์ด์Šˆ ์„น์…˜ ๋ณต์›**, ์•ก์…˜ ์•„์ดํ…œ์— **์‚ฐ์ถœ๋ฌผ ์ปฌ๋Ÿผ ์ถ”๊ฐ€**(๋‹ด๋‹นยท์ž‘์—…ยท๊ธฐํ•œยท์‚ฐ์ถœ๋ฌผ 4์š”์†Œ), ๋‹ด๋‹น์ž ๊ฐœ์ธ ์šฐ์„ (์กฐ์ง ๋‹จ์œ„ ์ง€์–‘), Executive Summary ๊ฒฐ๊ณผ ์ค‘์‹ฌ. ([meetPrompt.ts](src/features/datacollect/prompts/meetPrompt.ts)) +- **ํ•˜์œ„ํ˜ธํ™˜**: ์•ก์…˜ ํ‘œ ํŒŒ์„œ๊ฐ€ ์‹ 6์ปฌ๋Ÿผ/๊ตฌ5์ปฌ๋Ÿผ ๋ชจ๋‘ ํŒŒ์‹ฑ, ์„น์…˜ ๋ฒˆํ˜ธ ๋ฌด๊ด€ ํƒ์ง€. ์บ˜๋ฆฐ๋” ํ™•์‹  ๊ฒŒ์ดํŠธยท๊ทผ๊ฑฐ ์ธ์šฉ ์œ ์ง€. ํ…Œ์ŠคํŠธ +13๊ฑด(ํŒŒ์„œยทdegeneration ๊ฐ์ง€), ์ „์ฒด 659 ํ†ต๊ณผ. + +## v2.2.251 (2026-06-17) +### ๐Ÿ“ /meet ํšŒ์˜๋ก ์ถœ๋ ฅ๋ฌผ ๊ฐœ์„  (์‹ค๋ฌด ํšŒ์˜๋ก ๊ฐ€์ด๋“œ ๋ฐ˜์˜) +- **๊ฒฐ์ •์‚ฌํ•ญ โ†” ๋…ผ์˜์‚ฌํ•ญ ๋ถ„๋ฆฌ(์ตœ์šฐ์„  ์›์น™)**: ๊ธฐ์กด "์ฃผ์š” ๋…ผ์˜ ์‚ฌํ•ญ" ์•ˆ์— ๊ฒฐ์ •์ด ์„ž์ด๊ณ  "๊ฒฐ์ • ์‚ฌํ•ญ"๊ณผ ์ค‘๋ณต๋˜๋˜ ๊ตฌ์กฐ๋ฅผ, **๊ฒฐ์ • ์‚ฌํ•ญ(๋ช…์‹œ ํ•ฉ์˜๋งŒ) / ๋…ผ์˜ ์‚ฌํ•ญ(์ถ”๊ฐ€ ๊ฒ€ํ†  ํ•„์š”)**๋กœ ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌ. +- **๊ตฌ์กฐ ์žฌํŽธ**: โ‘  ํšŒ์˜ ๊ฐœ์š”(+**ํšŒ์˜ ๋ชฉ์ ** ์‹ ๊ทœ) โ†’ โ‘ก ์ฃผ์š” ๊ฒฐ๊ณผ(Executive Summary, "๋น„์ฐธ์„์ž๊ฐ€ ์ด๊ฒƒ๋งŒ ์ฝ๊ณ  ๊ฒฐ๊ณผ ํŒŒ์•…" ๊ธฐ์ค€) โ†’ โ‘ข ๊ฒฐ์ • ์‚ฌํ•ญ โ†’ โ‘ฃ ๋…ผ์˜ ์‚ฌํ•ญ(์•ˆ๊ฑด๋ณ„ ํ˜„ํ™ฉ/ํ•ต์‹ฌ๋…ผ์˜/์ถ”๊ฐ€๊ฒ€ํ† ) โ†’ โ‘ค **๋ฆฌ์Šคํฌ ๋ฐ ๊ฒ€ํ†  ์‚ฌํ•ญ(๋ฆฌ์Šคํฌยท์˜ํ–ฅ๋„ยท๋Œ€์‘๋ฐฉ์•ˆ ํ‘œ)** โ†’ โ‘ฅ ์•ก์…˜ ์•„์ดํ…œ. +- **๊ฒฐ๊ณผ ์ค‘์‹ฌ ์„œ์ˆ **: ๋ฐœ์–ธ ๋‚˜์—ด ๊ธˆ์ง€(๋ˆ„๊ฐ€ ๋ฌด์Šจ ๋ง ํ–ˆ๋Š”์ง€ X), ์ฑ…์ž„ ์†Œ์žฌยท์ž…์žฅ ์ฐจ์ด๊ฐ€ ํ•ต์‹ฌ์ผ ๋•Œ๋งŒ ๋ฐœ์–ธ์ž ํ‘œ๊ธฐ. ์‚ฌ์‹ค ์ค‘์‹ฌยท์ฆ๋น™ ๋ณด์กด ์›์น™๊ณผ ์ถœ๋ ฅ ์ „ ํ’ˆ์งˆ ์ฒดํฌ๋ฆฌ์ŠคํŠธ๋ฅผ ํ”„๋กฌํ”„ํŠธ์— ๋‚ด์žฌํ™”. ([meetPrompt.ts](src/features/datacollect/prompts/meetPrompt.ts)) +- **๋‹ค์šด์ŠคํŠธ๋ฆผ ํ•˜์œ„ํ˜ธํ™˜**: ํšŒ์˜์ผ ์ถ”์ถœ `**๋‚ ์งœ**`โ†’`**์ผ์‹œ**`(๋‘˜ ๋‹ค ์ธ์‹), ์•ก์…˜ ์„น์…˜ ํŒŒ์„œ๋ฅผ ๋ฒˆํ˜ธ ๋ฌด๊ด€์œผ๋กœ ๋ณ€๊ฒฝ(`(?:\d+\.)?์•ก์…˜ ์•„์ดํ…œ`) โ€” ์บ˜๋ฆฐ๋” ํ™•์‹  ๊ฒŒ์ดํŠธยท๊ทผ๊ฑฐ ์ธ์šฉยท๋ฐœ์–ธ ๊ท€์† ์•ˆ์ „์žฅ์น˜ ์ „๋ถ€ ์œ ์ง€. ([calendarHelpers.ts](src/features/datacollect/scheduling/calendarHelpers.ts)) +- ํšŒ๊ท€ ํ…Œ์ŠคํŠธ 32๊ฑด(meetRegistrationยทcalendarApi) ํ†ต๊ณผ, ํƒ€์ž…์ฒดํฌ 0 ์—๋Ÿฌ. + ## v2.2.213 (2026-06-11) ### ๐Ÿง  Self-Evolving Digital Employee OS P0~P6 (์‹ ๊ทœ ๋ชจ๋“ˆ 17์ข…, `src/intelligence/`) - **์‹ ๋ขฐ์„ฑ ์ฝ”์–ด**: Requirement Graph(์—…๋ฌด๋ณ„ ํ•„์ˆ˜ ์š”์†Œ ์ฒดํฌ๋ฆฌ์ŠคํŠธ ์ฃผ์ž… + ๋‹ต๋ณ€ ์ปค๋ฒ„๋ฆฌ์ง€ footer) ยท Confidence Engine(ํ™•์‹ ๋„ 0~100 footer) ยท Escalation Engine(์ €ํ™•์‹ ยท์ถฉ๋Œยท์ถœ์ฒ˜๋ˆ„๋ฝ ์‹œ ๊ฒ€ํ†  ์š”์ฒญ) ยท Epistemic Guard(๋ชจ๋ฆ„/์ถ”์ •/ํ™•์‹ค 3๋ถ„๋ฅ˜ ๊ฐ•์ œ) ยท Provenance(์ถœ์ฒ˜ ์ˆ˜์ •์ผยท์˜ค๋ž˜๋จ ๊ฒฝ๊ณ ) ยท Critic Loop(๋ฌธ์ œ ์‹ ํ˜ธ turn ๋งŒ LLM ๊ฒ€์ˆ˜ 1ํšŒ) diff --git a/package.json b/package.json index 128c45c..d83d5b0 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "astra", "displayName": "Astra", "description": "The personal intelligence layer for Antigravity and VS Code. A private cognitive partner for deep project context, memory, and proactive strategic decision-making.", - "version": "2.2.250", + "version": "2.2.253", "publisher": "g1nation", "license": "MIT", "icon": "assets/icon.png", diff --git a/src/features/datacollect/handlers.ts b/src/features/datacollect/handlers.ts index cf84233..81a0ae9 100644 --- a/src/features/datacollect/handlers.ts +++ b/src/features/datacollect/handlers.ts @@ -619,16 +619,56 @@ async function runMeet(arg: string, view: Webview | undefined, context?: vscode. chunk(view, `๐Ÿงฉ **๊ธด ๋…น์ทจ๋ก โ€” 2๋‹จ๊ณ„ ํ•ฉ์„ฑ** (์กฐ๊ฐ ${segments.length}๊ฐœ ร— ~${(SEG_SIZE / 1000) | 0}K์ž, ๋ชจ๋ธ \`${model}\`)\n`); const extractSystem = '๋‹น์‹ ์€ ํšŒ์˜ ๋…น์ทจ ์‚ฌ์‹ค ์ถ”์ถœ๊ธฐ์ž…๋‹ˆ๋‹ค. ์ œ๊ณต๋œ ์กฐ๊ฐ์— ๋ช…์‹œ๋œ ๋‚ด์šฉ๋งŒ ํ˜•์‹๋Œ€๋กœ ์ถ”์ถœํ•˜๊ณ , ์—†๋Š” ์‚ฌ์‹ค์„ ๋งŒ๋“ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ์ถœ๋ ฅ์€ ํ•œ๊ตญ์–ด์ž…๋‹ˆ๋‹ค.'; const notes: string[] = []; + let failedSegs = 0; + // ์•ฝํ•œ ๋ชจ๋ธ์ด ํฐ ์กฐ๊ฐ์—์„œ ์ถœ๋ ฅ ๋ถ•๊ดด(๋ฐ˜๋ณต/๊นจ์ง)ํ•  ๋•Œ, ๊ทธ ์กฐ๊ฐ์„ ์ค„ ๊ฒฝ๊ณ„๋กœ + // ์ ˆ๋ฐ˜์”ฉ ์ชผ๊ฐœ ์žฌ๊ท€ ์žฌ์‹œ๋„ํ•œ๋‹ค. ์ž…๋ ฅ์„ ์ค„์ด๋ฉด ๋ถ•๊ดด ํ™•๋ฅ ์ด ๋–จ์–ด์ง€๋ฏ€๋กœ ๋ชจ๋ธ + // ๊ต์ฒด ์—†์ด๋„ ์„ฑ๊ณต๋ฅ ์ด ์˜ค๋ฅธ๋‹ค. MIN_SEG ์ดํ•˜์ธ๋ฐ๋„ ์‹คํŒจํ•˜๋ฉด ๊ทธ ๊ตฌ๊ฐ„๋งŒ ํฌ๊ธฐ. + const MIN_SEG = 3500; + const extractSeg = async (seg: string, label: string, idx: number, total: number): Promise => { + try { + // callLmSynthesis ๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ์žฌ์‹œ๋„(๋ฐ˜๋ณต ์–ต์ œ ๊ฐ•ํ™”)๊นŒ์ง€ ์ˆ˜ํ–‰ํ•œ๋‹ค. + const note = await callLmSynthesis(buildMeetExtractPrompt(seg, metadata, idx, total), extractSystem); + if (!note) throw new Error('์ถ”์ถœ ๊ฒฐ๊ณผ๊ฐ€ ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค.'); + return note.trim(); + } catch (segErr: any) { + if (seg.length <= MIN_SEG) { + chunk(view, ` โš ๏ธ ์กฐ๊ฐ ${label} ์ถ”์ถœ ์‹คํŒจ(์ตœ์†Œ ํฌ๊ธฐ ๋„๋‹ฌ, ๊ฑด๋„ˆ๋œ€): ${segErr?.message || String(segErr)}\n`); + return null; + } + chunk(view, ` โ†ฉ๏ธŽ ์กฐ๊ฐ ${label}(${seg.length.toLocaleString()}์ž) ์ถœ๋ ฅ ๋ถ•๊ดด โ†’ ์ ˆ๋ฐ˜์œผ๋กœ ์ชผ๊ฐœ ์žฌ์‹œ๋„\n`); + const lines = seg.split('\n'); + let cut = 0, acc = 0; + for (; cut < lines.length - 1; cut++) { acc += lines[cut].length + 1; if (acc >= seg.length / 2) break; } + const left = lines.slice(0, cut + 1).join('\n'); + const right = lines.slice(cut + 1).join('\n'); + if (!left.trim() || !right.trim()) return null; // ๋”๋Š” ๋ชป ์ชผ๊ฐฌ + const parts = [ + await extractSeg(left, `${label}a`, idx, total), + await extractSeg(right, `${label}b`, idx, total), + ].filter(Boolean) as string[]; + return parts.length ? parts.join('\n\n') : null; + } + }; for (let i = 0; i < segments.length; i++) { - chunk(view, ` โณ ์กฐ๊ฐ ${i + 1}/${segments.length} ์ถ”์ถœ ์ค‘โ€ฆ`); + chunk(view, ` โณ ์กฐ๊ฐ ${i + 1}/${segments.length} ์ถ”์ถœ ์ค‘โ€ฆ\n`); const t0 = Date.now(); - const note = await callLmSynthesis( - buildMeetExtractPrompt(segments[i], metadata, i + 1, segments.length), - extractSystem, - ); - if (!note) throw new Error(`์กฐ๊ฐ ${i + 1} ์ถ”์ถœ ๊ฒฐ๊ณผ๊ฐ€ ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค.`); - notes.push(`### โ”€โ”€โ”€ ์กฐ๊ฐ ${i + 1}/${segments.length} ๋…ธํŠธ โ”€โ”€โ”€\n${note.trim()}`); - chunk(view, ` โœ“ (${Math.round((Date.now() - t0) / 1000)}s)\n`); + const note = await extractSeg(segments[i], String(i + 1), i + 1, segments.length); + if (note) { + notes.push(`### โ”€โ”€โ”€ ์กฐ๊ฐ ${i + 1}/${segments.length} ๋…ธํŠธ โ”€โ”€โ”€\n${note}`); + chunk(view, ` โœ“ ์กฐ๊ฐ ${i + 1} ์™„๋ฃŒ (${Math.round((Date.now() - t0) / 1000)}s)\n`); + } else { + // ํ•œ ์กฐ๊ฐ์ด ๋๋‚ด ์‹คํŒจํ•ด๋„ ์ „์ฒด ํšŒ์˜๋ก์„ ํฌ๊ธฐํ•˜์ง€ ์•Š๋Š”๋‹ค โ€” ๋ˆ„๋ฝ์„ + // ๋ช…์‹œํ•˜๊ณ  ๋‚˜๋จธ์ง€ ์กฐ๊ฐ์œผ๋กœ ๋ถ€๋ถ„ ํšŒ์˜๋ก์„ ๋งŒ๋“ ๋‹ค. + failedSegs++; + notes.push(`### โ”€โ”€โ”€ ์กฐ๊ฐ ${i + 1}/${segments.length} ๋…ธํŠธ โ”€โ”€โ”€\n(์ด ๊ตฌ๊ฐ„์€ ๋ชจ๋ธ ์ถœ๋ ฅ ์˜ค๋ฅ˜๋กœ ์ถ”์ถœํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.)`); + chunk(view, ` โš ๏ธ ์กฐ๊ฐ ${i + 1} ์ถ”์ถœ ์‹คํŒจ(๊ฑด๋„ˆ๋œ€)\n`); + } + } + if (failedSegs === segments.length) { + throw new Error(`๋ชจ๋“  ์กฐ๊ฐ(${segments.length}๊ฐœ) ์ถ”์ถœ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค โ€” ๋ชจ๋ธ ์ถœ๋ ฅ์ด ๊ณ„์† ๋ถ•๊ดดํ•ฉ๋‹ˆ๋‹ค.`); + } + if (failedSegs > 0) { + chunk(view, `\nโš ๏ธ ${segments.length}๊ฐœ ์ค‘ ${failedSegs}๊ฐœ ๊ตฌ๊ฐ„์„ ์ถ”์ถœํ•˜์ง€ ๋ชปํ•ด **๋ถ€๋ถ„ ํšŒ์˜๋ก**์œผ๋กœ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋” ํฐ ๋ชจ๋ธ(์˜ˆ: 27B+) ์‚ฌ์šฉ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.\n`); } groundingNotes = notes.join('\n\n'); // โ”€โ”€ Reduce: ๋…ธํŠธ ๋ณ‘ํ•ฉ โ†’ ์ตœ์ข… ํšŒ์˜๋ก โ”€โ”€ @@ -639,7 +679,17 @@ async function runMeet(arg: string, view: Webview | undefined, context?: vscode. chunk(view, ` โœ“ (${Math.round((Date.now() - t1) / 1000)}s)\n\n`); } } catch (e: any) { - chunk(view, `\n\nโš ๏ธ ํšŒ์˜๋ก ํ•ฉ์„ฑ ์‹คํŒจ: ${e?.message || String(e)}\n(LM ์„œ๋ฒ„๊ฐ€ ๋–  ์žˆ๋Š”์ง€, \`g1nation.ollamaUrl\` / \`defaultModel\` ์„ค์ •์„ ํ™•์ธํ•˜์„ธ์š”.)\n\n`); + const msg = e?.message || String(e); + const degen = /๋ถ•๊ดด|๋ฐ˜๋ณต|๊นจ|parse input/i.test(msg); + chunk(view, `\n\nโš ๏ธ ํšŒ์˜๋ก ํ•ฉ์„ฑ ์‹คํŒจ: ${msg}\n`); + if (degen) { + chunk(view, `\n๐Ÿ’ก ๋ชจ๋ธ ์ถœ๋ ฅ์ด ๋ฐ˜๋ณต/๊นจ์ง(degeneration)์œผ๋กœ ๋ถ•๊ดดํ•œ ๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค. ์žฌ์‹œ๋„(๋ฐ˜๋ณต ์–ต์ œ ๊ฐ•ํ™”)์—๋„ ํ’€๋ฆฌ์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.\n` + + ` ยท ํ˜„์žฌ ๋ชจ๋ธ \`${model}\` ์ด ๊ธด ํ•œ๊ตญ์–ด ๋…น์ทจ๋ก์—๋Š” ์•ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค โ€” **๋” ํฐ ๋ชจ๋ธ(27B+๊ธ‰)** ๋กœ \`g1nation.defaultModel\` ๋ณ€๊ฒฝ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.\n` + + ` ยท ๋˜๋Š” ๋…น์ทจ๋ก์„ ๋” ์งง๊ฒŒ ๋‚˜๋ˆ  ์—ฌ๋Ÿฌ ๋ฒˆ \`/meet\` ํ•˜๋ฉด ์„ฑ๊ณต๋ฅ ์ด ์˜ฌ๋ผ๊ฐ‘๋‹ˆ๋‹ค.\n`); + } else { + chunk(view, `(LM ์„œ๋ฒ„๊ฐ€ ๋–  ์žˆ๋Š”์ง€, \`g1nation.ollamaUrl\` / \`defaultModel\` ์„ค์ •์„ ํ™•์ธํ•˜์„ธ์š”.)\n`); + } + chunk(view, '\n'); return true; } @@ -729,7 +779,7 @@ async function runMeet(arg: string, view: Webview | undefined, context?: vscode. if (cls.route === 'hold') { holds.push({ idx: holds.length + 1, - owner: task.owner, work: task.work, detail: task.detail, due: task.due, + owner: task.owner, work: task.work, detail: task.detail, deliverable: task.deliverable, due: task.due, kind: cls.kind, condition: cls.condition, suggestedDate: cls.suggestedDate, }); continue; @@ -742,7 +792,7 @@ async function runMeet(arg: string, view: Webview | undefined, context?: vscode. if (past) extra.push('โš ๏ธ ๊ณผ๊ฑฐ ์ž๋ฃŒ ๊ธฐ๋ฐ˜ ๋“ฑ๋ก โ€” ์ด๋ฏธ ์™„๋ฃŒ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.'); if (cls.recurNote) extra.push(`โ†ป ๋ฐ˜๋ณต ์—…๋ฌด ์–ธ๊ธ‰(${cls.recurNote}) โ€” ์ •์ฑ…์ƒ ์ฒซ 1ํšŒ๋งŒ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.`); const notes = buildNotes({ - detail: task.detail, meetTitle, owner: task.owner, + detail: task.detail, meetTitle, owner: task.owner, deliverable: task.deliverable, dueRaw: task.due, dateLabel: cls.date, extra, }); const r = await registerAction(context, { diff --git a/src/features/datacollect/llm.ts b/src/features/datacollect/llm.ts index 6b8038f..f8af14a 100644 --- a/src/features/datacollect/llm.ts +++ b/src/features/datacollect/llm.ts @@ -14,20 +14,35 @@ import * as vscode from 'vscode'; import { bridgeFetch, BRIDGE_API } from './bridgeClient'; /** - * Bridge `/api/lm` ํ”„๋ก์‹œ๋กœ OpenAI ํ˜ธํ™˜ chat completion 1ํšŒ ํ˜ธ์ถœ. - * LLM ์„œ๋ฒ„/๋ชจ๋ธ์€ Astra ์„ค์ •(g1nation.ollamaUrl / defaultModel)์„ ์‚ฌ์šฉํ•œ๋‹ค. + * ๋ชจ๋ธ ์ถœ๋ ฅ ๋ถ•๊ดด(degeneration) ๊ฐ์ง€ โ€” ์ž‘์€/์•ฝํ•œ ๋ชจ๋ธ์ด ๊ธด ํ•œ๊ตญ์–ด ์ž…๋ ฅ์—์„œ + * ๋ฐ˜๋ณต ๋ฃจํ”„๋‚˜ ํ† ํฐ ๊นจ์ง(์˜ˆ: "ํ†ค์„ ํ†ค์„ ํ†ค์„โ€ฆ", "์„œ๋น„์Šค ๊ธฐํ”„ํŠธ ์„œ๋น„์Šค ๊ธฐํ”„ํŠธโ€ฆ", + * ๊นจ์ง„ ์œ ๋‹ˆ์ฝ”๋“œ)์— ๋น ์ง€๋Š” ๊ฒฝ์šฐ๋ฅผ ์žก๋Š”๋‹ค. ์ด๋Ÿฐ ์ถœ๋ ฅ์€ ์‚ฌ์šฉ ๋ถˆ๊ฐ€์ด๋ฏ€๋กœ ์žฌ์‹œ๋„ ํŠธ๋ฆฌ๊ฑฐ. */ -export async function callLmSynthesis(prompt: string, systemPrompt?: string): Promise { - const cfg = vscode.workspace.getConfiguration('g1nation'); - const lmUrl = (cfg.get('ollamaUrl', 'http://127.0.0.1:11434') || 'http://127.0.0.1:11434').replace(/\/$/, ''); - const model = (cfg.get('defaultModel', '') || 'gemma4:e2b').trim(); - const temperature = Math.max(0, Math.min(2, cfg.get('datacollectSynthesisTemperature', 0.1) ?? 0.1)); - const baseSys = systemPrompt - || '๋‹น์‹ ์€ ์‹œ๋‹ˆ์–ด UX/UI ๋ถ„์„๊ฐ€์ด์ž ํ”„๋ก ํŠธ์—”๋“œ ์•„ํ‚คํ…ํŠธ๋‹ค. ๋ชจ๋“  ๋ณด๊ณ ์„œ๋Š” ํ•œ๊ตญ์–ด๋กœ, ์ œ๊ณต๋œ JSON ์Šค์บ” ๋ฐ์ดํ„ฐ์˜ ๊ตฌ์ฒด์  ์ˆ˜์น˜๋ฅผ ์ธ์šฉํ•ด ์ž‘์„ฑํ•œ๋‹ค. ์›๋ณธ ๋ ˆํผ๋Ÿฐ์Šค ์‚ฌ์ดํŠธ๋ฅผ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ๋ช…์„ธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ๋ฏธ์…˜์ด๋ฉฐ, ๋‹ค๋ฅธ ์„œ๋น„์Šค๋กœ ์žฌํ•ด์„ยทํ™•์žฅํ•˜์ง€ ์•Š๋Š”๋‹ค.'; - const sys = baseSys + '\n\n[์ถœ๋ ฅ ์œ„์ƒ ๊ทœ์น™ โ€” ๋ฐ˜๋“œ์‹œ ์ค€์ˆ˜]\n' - + '- ์ž์—ฐ์Šค๋Ÿฌ์šด ํ•œ๊ตญ์–ด๋กœ ์ž‘์„ฑํ•˜๊ณ , ํ•œ ๋‹จ์–ด ์•ˆ์— ํ•œ๊ธ€๊ณผ ์˜๋ฌธ ์•ŒํŒŒ๋ฒณ์„ ์„ž์ง€ ๋งˆ์‹œ์˜ค ("๊ฒฐently", "์ธorp" ๊ฐ™์€ ๊นจ์ง„ ํ•ฉ์„ฑ ํ‘œ๊ธฐ ์ ˆ๋Œ€ ๊ธˆ์ง€).\n' - + '- ์™ธ๋ž˜์–ดยท๊ธฐ์ˆ  ์šฉ์–ด๋Š” ์™„์ „ํ•œ ํ•œ๊ธ€ ํ‘œ๊ธฐ ๋˜๋Š” ์™„์ „ํ•œ ์˜๋ฌธ ๋‹จ์–ด ์ค‘ ํ•˜๋‚˜๋กœ ์ผ๊ด€๋˜๊ฒŒ ์“ฐ์‹œ์˜ค.\n' - + '- [Self-Reflector Check], Consistency/Completeness/Accuracy ๊ฐ™์€ ๋‚ด๋ถ€ ๊ฒ€์ฆยท์ฒดํฌ ๋กœ๊ทธ ๋ธ”๋ก์„ ์ถœ๋ ฅ์— ์ ˆ๋Œ€ ํฌํ•จํ•˜์ง€ ๋งˆ์‹œ์˜ค. ์ตœ์ข… ์‚ฌ์šฉ์ž ๊ฒฐ๊ณผ๋ฌผ๋งŒ ์ถœ๋ ฅํ•˜์‹œ์˜ค.'; +export function looksDegenerate(text: string): boolean { + if (!text) return false; + // 1) ๋Œ€์ฒด๋ฌธ์ž(๏ฟฝ๏ฟฝ๏ฟฝ) โ€” ์ธ์ฝ”๋”ฉ/ํ† ํฐ ๊นจ์ง์˜ ํ™•์‹คํ•œ ์‹ ํ˜ธ + if (/๏ฟฝ/.test(text)) return true; + const compact = text.replace(/\s+/g, ' '); + // 2) ๊ฐ™์€ ๊ตฌ์ ˆ(3~20์ž)์ด 5ํšŒ ์ด์ƒ ๋ฐ˜๋ณต(์‚ฌ์ด ๊ณต๋ฐฑ ํ—ˆ์šฉ) โ€” ๋‹จ, ํ‘œ ๊ตฌ๋ถ„์„ ยท๊ธฐํ˜ธ ๋ฐ˜๋ณต์€ ์ œ์™ธ + const rep = compact.match(/(.{3,20}?)(?:\s*\1){4,}/); + if (rep && !/^[\s\-|=_.ยทโ€ข*#]+$/.test(rep[1])) return true; + // 3) ํ•œ๊ธ€+์˜๋ฌธ ๊นจ์ง ํ•ฉ์„ฑ ํ‘œ๊ธฐ๊ฐ€ ๊ณผ๋‹ค (์ •์ƒ ๊ต์ • ํ•œ๋„๋ฅผ ํฌ๊ฒŒ ์ดˆ๊ณผ) + if ((text.match(/[๊ฐ€-ํžฃ][a-z]{2,}/gi) || []).length > 12) return true; + return false; +} + +interface LmOpts { + /** ์‹คํŒจยท๋นˆ์‘๋‹ตยทdegeneration ์‹œ ์žฌ์‹œ๋„ ํšŸ์ˆ˜ (๊ธฐ๋ณธ 2 = ์ตœ๋Œ€ 3ํšŒ ์‹œ๋„). */ + retries?: number; + /** ๊ธฐ๋ณธ ์ƒ˜ํ”Œ๋ง ์˜จ๋„ override. */ + temperature?: number; +} + +/** bridge `/api/lm` ๋‹จ๋ฐœ ํ˜ธ์ถœ โ€” ์ƒ˜ํ”Œ๋ง ํŒŒ๋ผ๋ฏธํ„ฐ๊นŒ์ง€ ๋ฐ›๋Š” ๋‚ด๋ถ€ ์ฝ”์–ด. */ +async function callLmOnce( + lmUrl: string, model: string, sys: string, prompt: string, + sampling: { temperature: number; repeat_penalty: number; top_k: number }, +): Promise { const res = await bridgeFetch(BRIDGE_API.lm.proxy, { method: 'POST', body: JSON.stringify({ @@ -38,10 +53,10 @@ export async function callLmSynthesis(prompt: string, systemPrompt?: string): Pr { role: 'system', content: sys }, { role: 'user', content: prompt }, ], - temperature, + temperature: sampling.temperature, top_p: 0.85, - top_k: 20, - repeat_penalty: 1.1, + top_k: sampling.top_k, + repeat_penalty: sampling.repeat_penalty, }, }), }, { timeoutMs: 120_000 }); @@ -50,13 +65,58 @@ export async function callLmSynthesis(prompt: string, systemPrompt?: string): Pr ?? res?.answer ?? res?.response ?? ''; - let out = String(content) + return String(content) .replace(/\n*(?:```[\w]*\s*)?\[Self-Reflector Check\][\s\S]*$/i, '') .trim(); - if (/[๊ฐ€-ํžฃ][a-z]{2,}/.test(out)) { - out = await repairKoreanGlitches(out, lmUrl, model); +} + +/** + * Bridge `/api/lm` ํ”„๋ก์‹œ๋กœ OpenAI ํ˜ธํ™˜ chat completion ํ˜ธ์ถœ. + * LLM ์„œ๋ฒ„/๋ชจ๋ธ์€ Astra ์„ค์ •(g1nation.ollamaUrl / defaultModel)์„ ์‚ฌ์šฉํ•œ๋‹ค. + * + * v2.2.252: ์•ฝํ•œ ๋กœ์ปฌ ๋ชจ๋ธ์˜ ์ถœ๋ ฅ ๋ถ•๊ดด(๋ฐ˜๋ณตยท๊นจ์ง)์™€ LM ์„œ๋ฒ„ ์ผ์‹œ ์˜ค๋ฅ˜์— ๋Œ€๋น„ํ•ด + * ์žฌ์‹œ๋„๋ฅผ ๋‚ด์žฅํ•œ๋‹ค. ์žฌ์‹œ๋„ํ• ์ˆ˜๋ก repeat_penalty ๋ฅผ ์˜ฌ๋ฆฌ๊ณ  top_k ๋ฅผ ์ขํ˜€ ๋ฐ˜๋ณต + * ๋ฃจํ”„๋ฅผ ๊นฌ๋‹ค. ๋ชจ๋“  ์‹œ๋„๊ฐ€ ์‹คํŒจํ•˜๋ฉด ๋งˆ์ง€๋ง‰ ์—๋Ÿฌ๋ฅผ ๊ทธ๋Œ€๋กœ throw (ํ˜ธ์ถœ๋ถ€๊ฐ€ ์ฒ˜๋ฆฌ). + */ +export async function callLmSynthesis(prompt: string, systemPrompt?: string, opts?: LmOpts): Promise { + const cfg = vscode.workspace.getConfiguration('g1nation'); + const lmUrl = (cfg.get('ollamaUrl', 'http://127.0.0.1:11434') || 'http://127.0.0.1:11434').replace(/\/$/, ''); + const model = (cfg.get('defaultModel', '') || 'gemma4:e2b').trim(); + const baseTemp = opts?.temperature ?? Math.max(0, Math.min(2, cfg.get('datacollectSynthesisTemperature', 0.1) ?? 0.1)); + const baseSys = systemPrompt + || '๋‹น์‹ ์€ ์‹œ๋‹ˆ์–ด UX/UI ๋ถ„์„๊ฐ€์ด์ž ํ”„๋ก ํŠธ์—”๋“œ ์•„ํ‚คํ…ํŠธ๋‹ค. ๋ชจ๋“  ๋ณด๊ณ ์„œ๋Š” ํ•œ๊ตญ์–ด๋กœ, ์ œ๊ณต๋œ JSON ์Šค์บ” ๋ฐ์ดํ„ฐ์˜ ๊ตฌ์ฒด์  ์ˆ˜์น˜๋ฅผ ์ธ์šฉํ•ด ์ž‘์„ฑํ•œ๋‹ค. ์›๋ณธ ๋ ˆํผ๋Ÿฐ์Šค ์‚ฌ์ดํŠธ๋ฅผ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ๋ช…์„ธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ๋ฏธ์…˜์ด๋ฉฐ, ๋‹ค๋ฅธ ์„œ๋น„์Šค๋กœ ์žฌํ•ด์„ยทํ™•์žฅํ•˜์ง€ ์•Š๋Š”๋‹ค.'; + const sys = baseSys + '\n\n[์ถœ๋ ฅ ์œ„์ƒ ๊ทœ์น™ โ€” ๋ฐ˜๋“œ์‹œ ์ค€์ˆ˜]\n' + + '- ์ž์—ฐ์Šค๋Ÿฌ์šด ํ•œ๊ตญ์–ด๋กœ ์ž‘์„ฑํ•˜๊ณ , ํ•œ ๋‹จ์–ด ์•ˆ์— ํ•œ๊ธ€๊ณผ ์˜๋ฌธ ์•ŒํŒŒ๋ฒณ์„ ์„ž์ง€ ๋งˆ์‹œ์˜ค ("๊ฒฐently", "์ธorp" ๊ฐ™์€ ๊นจ์ง„ ํ•ฉ์„ฑ ํ‘œ๊ธฐ ์ ˆ๋Œ€ ๊ธˆ์ง€).\n' + + '- ์™ธ๋ž˜์–ดยท๊ธฐ์ˆ  ์šฉ์–ด๋Š” ์™„์ „ํ•œ ํ•œ๊ธ€ ํ‘œ๊ธฐ ๋˜๋Š” ์™„์ „ํ•œ ์˜๋ฌธ ๋‹จ์–ด ์ค‘ ํ•˜๋‚˜๋กœ ์ผ๊ด€๋˜๊ฒŒ ์“ฐ์‹œ์˜ค.\n' + + '- ๊ฐ™์€ ๋‹จ์–ดยท๊ตฌ์ ˆ์„ ๋ฐ˜๋ณตํ•˜์ง€ ๋ง๊ณ , ํ•œ ํ•ญ๋ชฉ์„ ๋‹ค ์“ฐ๋ฉด ๋‹ค์Œ ํ•ญ๋ชฉ์œผ๋กœ ์ง„ํ–‰ํ•˜์‹œ์˜ค (๋ฐ˜๋ณต ๋ฃจํ”„ ๊ธˆ์ง€).\n' + + '- [Self-Reflector Check], Consistency/Completeness/Accuracy ๊ฐ™์€ ๋‚ด๋ถ€ ๊ฒ€์ฆยท์ฒดํฌ ๋กœ๊ทธ ๋ธ”๋ก์„ ์ถœ๋ ฅ์— ์ ˆ๋Œ€ ํฌํ•จํ•˜์ง€ ๋งˆ์‹œ์˜ค. ์ตœ์ข… ์‚ฌ์šฉ์ž ๊ฒฐ๊ณผ๋ฌผ๋งŒ ์ถœ๋ ฅํ•˜์‹œ์˜ค.'; + + const maxRetries = Math.max(0, opts?.retries ?? 2); + let lastErr: unknown = null; + for (let attempt = 0; attempt <= maxRetries; attempt++) { + // ์žฌ์‹œ๋„ํ• ์ˆ˜๋ก ๋ฐ˜๋ณต์„ ๋” ๊ฐ•ํ•˜๊ฒŒ ์–ต์ œ: repeat_penalty โ†‘, top_k โ†“. + const sampling = { + temperature: baseTemp, + repeat_penalty: 1.1 + attempt * 0.15, // 1.1 โ†’ 1.25 โ†’ 1.4 + top_k: Math.max(10, 20 - attempt * 5), // 20 โ†’ 15 โ†’ 10 + }; + try { + let out = await callLmOnce(lmUrl, model, sys, prompt, sampling); + if (!out) { lastErr = new Error('LLM ์‘๋‹ต์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค.'); continue; } + if (looksDegenerate(out)) { + lastErr = new Error('๋ชจ๋ธ ์ถœ๋ ฅ์ด ๋ถ•๊ดด(๋ฐ˜๋ณต/๊นจ์ง)ํ–ˆ์Šต๋‹ˆ๋‹ค.'); + continue; // ๋‹ค์Œ ์‹œ๋„์—์„œ ๋ฐ˜๋ณต ์–ต์ œ ๊ฐ•ํ™” + } + if (/[๊ฐ€-ํžฃ][a-z]{2,}/.test(out)) { + out = await repairKoreanGlitches(out, lmUrl, model); + } + return out; + } catch (e) { + lastErr = e; + // bridge/LM ์„œ๋ฒ„ ์˜ค๋ฅ˜ โ€” ๋‹ค์Œ ์‹œ๋„๋กœ (degeneration ์œผ๋กœ ์ธํ•œ ์„œ๋ฒ„์ธก parse ์‹คํŒจ ํฌํ•จ) + } } - return out; + throw lastErr instanceof Error ? lastErr : new Error(String(lastErr)); } /** diff --git a/src/features/datacollect/prompts/meetPrompt.ts b/src/features/datacollect/prompts/meetPrompt.ts index f7f3c69..6837f9b 100644 --- a/src/features/datacollect/prompts/meetPrompt.ts +++ b/src/features/datacollect/prompts/meetPrompt.ts @@ -64,45 +64,68 @@ ${OUTPUT_FORMAT}`; } /** ์ตœ์ข… ํšŒ์˜๋ก ์ถœ๋ ฅ ํ˜•์‹ โ€” ๋‹จ์ผ์ƒท(buildMeetPrompt)๊ณผ ๋ณ‘ํ•ฉ ๋‹จ๊ณ„(buildMeetReducePrompt)๊ฐ€ ๊ณต์œ . */ -const OUTPUT_FORMAT = `# ์ถœ๋ ฅ ํ˜•์‹ (Output Format โ€” ์ •ํ™•ํžˆ ์ด ๊ตฌ์กฐ๋ฅผ ์œ ์ง€) +const OUTPUT_FORMAT = `# ์ž‘์„ฑ ์›์น™ (ํšŒ์˜๋ก ํ’ˆ์งˆ โ€” ์ถœ๋ ฅ ์ „ ๋ฐ˜๋“œ์‹œ ๋‚ด์žฌํ™”) +ํšŒ์˜๋ก์€ *๋Œ€ํ™” ์š”์•ฝ ๋ฌธ์„œ*๊ฐ€ ์•„๋‹ˆ๋ผ **ํ”„๋กœ์ ํŠธ ๊ด€๋ฆฌ ๋ฌธ์„œ**๋‹ค. ํšŒ์˜ ๋‚ด์šฉ ์ž์ฒด๋ณด๋‹ค "๋ฌด์—‡์ด ๊ฒฐ์ •๋๋Š”๊ฐ€ / ๋ˆ„๊ฐ€ ๋ฌด์—‡์„ ํ•ด์•ผ ํ•˜๋Š”๊ฐ€ / ๋ฌด์—‡์ด ์•„์ง ๊ฒฐ์ • ์•ˆ ๋๋Š”๊ฐ€"๋ฅผ ์šฐ์„ ํ•œ๋‹ค. +- **์ •๋ณด ์šฐ์„ ์ˆœ์œ„**: โ‘ ๊ฒฐ์ •์‚ฌํ•ญ โ‘ก์•ก์…˜์•„์ดํ…œ โ‘ข์˜คํ”ˆ ์ด์Šˆ โ‘ฃ๋ฆฌ์Šคํฌ โ‘ค๋…ผ์˜์‚ฌํ•ญ ์ˆœ์œผ๋กœ ์ค‘์š”ํ•˜๋‹ค. ๋ถ„๋Ÿ‰ยท์ •์„ฑ๋„ ์ด ์ˆœ์„œ๋กœ ๋ฐฐ๋ถ„ํ•œ๋‹ค. +- **์‚ฌ์‹ค ์ค‘์‹ฌ**: ์ถ”์ •ยทํ•ด์„ยทํ‰๊ฐ€๊ฐ€ ์•„๋‹ˆ๋ผ ํšŒ์˜์—์„œ ์‹ค์ œ ๋…ผ์˜ยทํ™•์ •๋œ ๊ฒƒ๋งŒ ์ ๋Š”๋‹ค. (์˜ˆ: "API ์—ฐ๋™์ด ๊ฐ€๋Šฅํ•  ๊ฒƒ์œผ๋กœ ํŒ๋‹จ๋จ"โŒ โ†’ "API ์—ฐ๋™ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ ๊ฒ€ํ†  ํ•„์š”"โœ…) +- **๊ฒฐ์ •์‚ฌํ•ญ โ†” ๋…ผ์˜์‚ฌํ•ญ ๊ตฌ๋ถ„(์ตœ์šฐ์„ )**: ๋ช…์‹œ์ ์œผ๋กœ ํ•ฉ์˜ยทํ™•์ •๋œ ๊ฒƒ๋งŒ '๊ฒฐ์ • ์‚ฌํ•ญ'์— ๋‘”๋‹ค. "๊ฒ€ํ†  ํ•„์š”/์ •์˜ ํ•„์š”/ํ™œ์šฉ ๊ฒ€ํ† "์ฒ˜๋Ÿผ *ํ™•์ •๋˜์ง€ ์•Š์€ ๊ฒƒ*์€ ๊ฒฐ์ •์‚ฌํ•ญ์ด ์•„๋‹ˆ๋‹ค โ€” ๋…ผ์˜ ์‚ฌํ•ญยท์˜คํ”ˆ ์ด์Šˆ๋กœ ๋‚ด๋ฆฐ๋‹ค. ๋ฐ˜๋Œ€๋กœ ์‹ค์ œ ํ™•์ •๋œ ๊ฒƒ์„ ๊ฒฐ์ •์‚ฌํ•ญ์—์„œ ๋ˆ„๋ฝํ•˜์ง€๋„ ๋ง ๊ฒƒ. +- **๊ฒฐ๊ณผ ์ค‘์‹ฌ**: ๋ฐœ์–ธ ์ˆœ์„œยทํ† ๋ก  ๊ณผ์ •ยท"๋ˆ„๊ฐ€ ๋ฌด์Šจ ๋ง์„ ํ–ˆ๋Š”์ง€"๋ฅผ ๋‚˜์—ดํ•˜์ง€ ๋ง๊ณ  *๊ฒฐ๊ณผ*๋ฅผ ์ ๋Š”๋‹ค. (์˜ˆ: "PlayCanvas๋ฅผ ๋…ผ์˜ํ•จ"โŒ โ†’ "PlayCanvas/Babylon.js ๋น„๊ต ๊ฒ€ํ†  ์ง„ํ–‰ ์˜ˆ์ •"โœ…) ์ฑ…์ž„ ์†Œ์žฌ๋‚˜ ์ž…์žฅ ์ฐจ์ด๊ฐ€ ํ•ต์‹ฌ์ผ ๋•Œ๋งŒ ๋ฐœ์–ธ์ž๋ฅผ ๋ฐํžŒ๋‹ค. +- **๋‹ด๋‹น์ž๋Š” ๊ฐœ์ธ ์šฐ์„ **: "๊ฐœ๋ฐœํŒ€/QAํŒ€" ๊ฐ™์€ ์กฐ์ง ๋‹จ์œ„๊ฐ€ ์•„๋‹ˆ๋ผ ๊ฐœ์ธ ์ด๋ฆ„(์˜ˆ: ์†ก๋ณ‘์ค€, ๊น€์›์ผ PD)์œผ๋กœ ์ ๋Š”๋‹ค. ์‹ค์ œ๋กœ ๊ฐœ์ธ์ด ์•ˆ ์ •ํ•ด์กŒ์œผ๋ฉด "๊ฐœ๋ฐœํŒ€ (๋‹ด๋‹น์ž ์ง€์ • ํ•„์š”)" ํ˜•ํƒœ๋กœ ํ‘œ๊ธฐ. +- **์ฆ๋น™ ๋ณด์กด**: ๊ฒฐ์ •๋˜์ง€ ์•Š์€ ์‚ฌํ•ญ์ด๋ผ๋„ ์ค‘์š”ํ•œ ๋…ผ์˜ยท๋ฆฌ์Šคํฌ๋Š” ๋น ์ง์—†์ด ๊ธฐ๋กํ•œ๋‹ค(ํ–ฅํ›„ ์ด๋ ฅยท๋ถ„์Ÿ ๊ทผ๊ฑฐ). + +# ์ถœ๋ ฅ ํ˜•์‹ (Output Format โ€” ์ •ํ™•ํžˆ ์ด ๊ตฌ์กฐ๋ฅผ ์œ ์ง€) # [ํšŒ์˜ ์ œ๋ชฉ] -- **๋‚ ์งœ**: [YYYY๋…„ MM์›” DD์ผ | ํ™•์ธ ๋ถˆ๊ฐ€] +## 1. ํšŒ์˜ ๊ฐœ์š” +- **์ผ์‹œ**: [YYYY๋…„ MM์›” DD์ผ | ํ™•์ธ ๋ถˆ๊ฐ€] - **์ฐธ์„์ž**: [๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๊ธฐ์ค€ | ์—†์„ ๊ฒฝ์šฐ: ๋…ผ์˜ ์ฐธ์—ฌ ์ฃผ์ฒด] -- **์ฃผ์ œ ์š”์•ฝ**: [ํ•œ ๋ฌธ์žฅ ์š”์•ฝ] +- **ํšŒ์˜ ๋ชฉ์ **: [์ด ํšŒ์˜๊ฐ€ ์™œ ์—ด๋ ธ๋Š”์ง€ ํ•œ ๋ฌธ์žฅ. ๋…น์ทจ๋ก์—์„œ ๋“œ๋Ÿฌ๋‚œ ๋ชฉ์ ๋งŒ ์ ๊ณ , ๋ถˆ๋ช…ํ™•ํ•˜๋ฉด "ํ™•์ธ ํ•„์š”"] -## ๐Ÿ”น ์š”์•ฝ ๋ณด๊ณ  -ํ•ต์‹ฌ ๋…ผ์˜ ์š”์•ฝ 3~5๊ฐœ๋ฅผ ๊ธ€๋จธ๋ฆฌํ‘œ๋กœ ์ž‘์„ฑ. - -## 1. ์ฃผ์š” ๋…ผ์˜ ์‚ฌํ•ญ -๊ฐ ์•ˆ๊ฑด๋งˆ๋‹ค ์•„๋ž˜ ๊ตฌ์กฐ๋กœ: -### [์•ˆ๊ฑด ์ œ๋ชฉ] -- **ํ˜„ํ™ฉ**: -- **ํ•ต์‹ฌ ๋…ผ์˜**: ์Ÿ์ ์ด ๋˜๊ฑฐ๋‚˜ ์ฃผ์ฒด๊ฐ€ ์ค‘์š”ํ•œ ๋ฐœ์–ธ์€ "OOO: ~" ํ˜•ํƒœ๋กœ ๋ฐœ์–ธ์ž๋ฅผ ๋ฐํžŒ๋‹ค. ์ฃผ์ฒด๊ฐ€ ๋ถˆ๋ช…ํ™•ํ•˜๋ฉด ์ด๋ฆ„์„ ๋ถ™์ด์ง€ ๋ง ๊ฒƒ. -- **๊ฒฐ๋ก **: [๊ฒฐ์ •๋จ / ๋…ผ์˜ ์ค‘ / ๋ณด๋ฅ˜] - -## 2. ๋ฆฌ์Šคํฌ ๋ฐ ์ด์Šˆ +## 2. ์ฃผ์š” ๊ฒฐ๊ณผ (Executive Summary) +ํšŒ์˜ *๊ฒฐ๊ณผ*๋ฅผ 3~5์ค„ ๊ธ€๋จธ๋ฆฌํ‘œ๋กœ ์š”์•ฝํ•œ๋‹ค. ํšŒ์˜ ๋‚ด์šฉ์„ ์„ค๋ช…ํ•˜์ง€ ๋ง๊ณ (์˜ˆ: "Babylon.js๋ฅผ ๊ฒ€ํ† ํ•จ"โŒ) ๊ฒฐ๊ณผ๋ฅผ ์ ๋Š”๋‹ค(์˜ˆ: "ํ…Œ์ŠคํŠธ ์ƒ˜ํ”Œ 3์ข… ์„ ์ •", "CCOC 1์ฐจ ์ž‘์—… 6/19๊นŒ์ง€ ์ง„ํ–‰"โœ…). **์ฐธ์„ํ•˜์ง€ ์•Š์€ ์ดํ•ด๊ด€๊ณ„์ž๊ฐ€ ์ด ํ•ญ๋ชฉ๋งŒ ์ฝ์–ด๋„ ๊ฒฐ๊ณผ๋ฅผ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.** ## 3. ๊ฒฐ์ • ์‚ฌํ•ญ +**๋ช…์‹œ์ ์œผ๋กœ ํ•ฉ์˜ยทํ™•์ •๋œ ๊ฒƒ๋งŒ** ์ ๋Š”๋‹ค (์ผ์ • ํ™•์ •ยท๊ฐœ๋ฐœ ๋ฒ”์œ„ ํ™•์ •ยท์ •์ฑ… ๋ณ€๊ฒฝยทํ›„์† ๋ฐฉํ–ฅ ํ™•์ • ๋“ฑ). ๊ฒ€ํ† ยท์ •์˜ยท๋„์ž…์—ฌ๋ถ€๊ฐ€ *ํ•„์š”*ํ•œ ๋‹จ๊ณ„์˜ ๊ฒƒ์€ ์—ฌ๊ธฐ ๋„ฃ์ง€ ๋ง๊ณ  '์˜คํ”ˆ ์ด์Šˆ'๋‚˜ '๋…ผ์˜ ์‚ฌํ•ญ'์œผ๋กœ ๋‚ด๋ฆฐ๋‹ค. ๊ฐ ๊ฒฐ์ • ๋์— ๊ทผ๊ฑฐ ๋ฐœ์–ธ์„ ์ธ์šฉํ•œ๋‹ค: \`- [๊ฒฐ์ • ๋‚ด์šฉ] โ€” ๊ทผ๊ฑฐ: "๋ฐœ์–ธ ์›๋ฌธ ์ผ๋ถ€"\` +(์ด๋ฒˆ ํšŒ์˜์—์„œ ํ™•์ •๋œ ๊ฒฐ์ •์ด ์—†์œผ๋ฉด "์ด๋ฒˆ ํšŒ์˜์—์„œ ํ™•์ •๋œ ๊ฒฐ์ •์‚ฌํ•ญ ์—†์Œ"์ด๋ผ๊ณ  ๋ช…์‹œํ•œ๋‹ค โ€” ๋นˆ์นธยท์ƒ๋žต ๊ธˆ์ง€.) -## 4. ์˜คํ”ˆ ์ด์Šˆ - -## 5. ์•ก์…˜ ์•„์ดํ…œ -๊ฐ ํ–‰์€ ๋ฐ˜๋“œ์‹œ ๋…น์ทจ๋ก ๊ทผ๊ฑฐ๋กœ ์ž‘์„ฑํ•œ๋‹ค. ํ‘œ ์…€ ์•ˆ์—์„œ๋Š” ์ค„๋ฐ”๊ฟˆ๊ณผ \`|\` ๋ฌธ์ž๋ฅผ ์“ฐ์ง€ ๋ง ๊ฒƒ. -| ๋‹ด๋‹น | ์ž‘์—… ๋‚ด์šฉ | ์ž‘์—… ์ƒ์„ธ | ๊ธฐํ•œ | ์ƒํƒœ | -| --- | --- | --- | --- | --- | +## 4. ์•ก์…˜ ์•„์ดํ…œ +ํšŒ์˜ ํ›„ ์ˆ˜ํ–‰ํ•  ์—…๋ฌด๋ฅผ **๋‹ด๋‹นยท์ž‘์—…ยท๊ธฐํ•œยท์‚ฐ์ถœ๋ฌผ 4์š”์†Œ**๊ฐ€ ๋ชจ๋‘ ๋“œ๋Ÿฌ๋‚˜๊ฒŒ ์ •๋ฆฌํ•œ๋‹ค. ๊ฐ ํ–‰์€ ๋ฐ˜๋“œ์‹œ ๋…น์ทจ๋ก ๊ทผ๊ฑฐ๋กœ ์ž‘์„ฑํ•œ๋‹ค. ๋‹ด๋‹น์ž๋Š” ๊ฐœ์ธ ์šฐ์„ (์—†์œผ๋ฉด "OOํŒ€ (๋‹ด๋‹น์ž ์ง€์ • ํ•„์š”)"). ํšŒ์˜์—์„œ ๊ธฐํ•œยท์‚ฐ์ถœ๋ฌผ์ด ์•ˆ ๋‚˜์™”์œผ๋ฉด ํ•ด๋‹น ์นธ์— "ํ™•์ธ ํ•„์š”"๋ผ๊ณ  ์ ๋Š”๋‹ค. ํ‘œ ์…€ ์•ˆ์—์„œ๋Š” ์ค„๋ฐ”๊ฟˆ๊ณผ \`|\` ๋ฌธ์ž๋ฅผ ์“ฐ์ง€ ๋ง ๊ฒƒ. +| ๋‹ด๋‹น | ์ž‘์—… ๋‚ด์šฉ | ์ž‘์—… ์ƒ์„ธ | ์‚ฐ์ถœ๋ฌผ | ๊ธฐํ•œ | ์ƒํƒœ | +| --- | --- | --- | --- | --- | --- | - **์ž‘์—… ๋‚ด์šฉ**: ํ•œ ์ค„์งœ๋ฆฌ ์ž‘์—…๋ช…. ์บ˜๋ฆฐ๋” ์ผ์ • ์ œ๋ชฉ์œผ๋กœ ๊ทธ๋Œ€๋กœ ์“ฐ์ด๋ฏ€๋กœ ๊ทธ ์ž์ฒด๋กœ ๋ฌด์Šจ ์ผ์ธ์ง€ ์‹๋ณ„๋˜๊ฒŒ ์ž‘์„ฑํ•œ๋‹ค. ("๊ฒ€ํ† ", "ํ™•์ธ" ๊ฐ™์€ ๋‹จ๋… ๋™์‚ฌ ๊ธˆ์ง€) -- **์ž‘์—… ์ƒ์„ธ**: ์ด ์ž‘์—…์ด **๋ฌด์—‡์ด๊ณ , ์™œ ํ•„์š”ํ•˜๋ฉฐ, ๊ตฌ์ฒด์ ์œผ๋กœ ๋ฌด์—‡์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•˜๋Š”์ง€**๋ฅผ 2~3๋ฌธ์žฅ์œผ๋กœ ์ ๋Š”๋‹ค(๋ฐฐ๊ฒฝยท๋ชฉ์ ยท์ˆ˜ํ–‰ ๋ฒ”์œ„ยท์‚ฐ์ถœ๋ฌผ). ๋…น์ทจ๋ก์—์„œ ์–ธ๊ธ‰๋œ ๋Œ€์ƒยท์ˆ˜์น˜ยท์กฐ๊ฑด์„ ๊ทธ๋Œ€๋กœ ์ธ์šฉํ•˜๊ณ , **๋งˆ์ง€๋ง‰์— ๊ทผ๊ฑฐ ๋ฐœ์–ธ ์›๋ฌธ ์ผ๋ถ€๋ฅผ \`๊ทผ๊ฑฐ: "โ€ฆ"\` ํ˜•ํƒœ๋กœ ๋ง๋ถ™์ธ๋‹ค**. ๊ทผ๊ฑฐ๊ฐ€ ๋ถ€์กฑํ•˜๋ฉด "์ถ”๊ฐ€ ํ™•์ธ ํ•„์š”: โ€ฆ" ํ˜•ํƒœ๋กœ ๋ฌด์—‡์„ ํ™•์ธํ•ด์•ผ ํ•˜๋Š”์ง€ ๋ช…์‹œํ•œ๋‹ค. ๋‹จ์ˆœํžˆ ์ž‘์—…๋ช…์„ ๋ฐ˜๋ณตํ•˜์ง€ ๋ง ๊ฒƒ. +- **์ž‘์—… ์ƒ์„ธ**: ์ด ์ž‘์—…์ด **๋ฌด์—‡์ด๊ณ , ์™œ ํ•„์š”ํ•˜๋ฉฐ, ๊ตฌ์ฒด์ ์œผ๋กœ ๋ฌด์—‡์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•˜๋Š”์ง€**๋ฅผ 2~3๋ฌธ์žฅ์œผ๋กœ ์ ๋Š”๋‹ค(๋ฐฐ๊ฒฝยท๋ชฉ์ ยท์ˆ˜ํ–‰ ๋ฒ”์œ„). ๋…น์ทจ๋ก์—์„œ ์–ธ๊ธ‰๋œ ๋Œ€์ƒยท์ˆ˜์น˜ยท์กฐ๊ฑด์„ ๊ทธ๋Œ€๋กœ ์ธ์šฉํ•˜๊ณ , **๋งˆ์ง€๋ง‰์— ๊ทผ๊ฑฐ ๋ฐœ์–ธ ์›๋ฌธ ์ผ๋ถ€๋ฅผ \`๊ทผ๊ฑฐ: "โ€ฆ"\` ํ˜•ํƒœ๋กœ ๋ง๋ถ™์ธ๋‹ค**. ๊ทผ๊ฑฐ๊ฐ€ ๋ถ€์กฑํ•˜๋ฉด "์ถ”๊ฐ€ ํ™•์ธ ํ•„์š”: โ€ฆ" ํ˜•ํƒœ๋กœ ๋ช…์‹œ. ๋‹จ์ˆœํžˆ ์ž‘์—…๋ช…์„ ๋ฐ˜๋ณตํ•˜์ง€ ๋ง ๊ฒƒ. +- **์‚ฐ์ถœ๋ฌผ**: ์ด ์ž‘์—…์ด ๋๋‚˜๋ฉด ๋‚˜์˜ค๋Š” ๊ตฌ์ฒด์  ๊ฒฐ๊ณผ๋ฌผ(์˜ˆ: ํ…Œ์ŠคํŠธ URL ๋ชฉ๋ก, ๋น„๊ต ๋ถ„์„ ๋ฌธ์„œ, ์ผ์ •ํ‘œ). ํšŒ์˜์—์„œ ์•ˆ ๋‚˜์™”์œผ๋ฉด "ํ™•์ธ ํ•„์š”". - **์ƒํƒœ**: ๋‹ค์Œ ์ค‘ ์ •ํ™•ํžˆ ํ•˜๋‚˜๋กœ ๋ถ„๋ฅ˜ํ•œ๋‹ค (์บ˜๋ฆฐ๋” ์ž๋™ ๋“ฑ๋ก ๊ฒŒ์ดํŠธ๊ฐ€ ์ด ๊ฐ’์œผ๋กœ ๋ถ„๊ธฐํ•˜๋ฏ€๋กœ ํ˜•์‹ ์—„์ˆ˜): - \`ํ™•์ •\` โ€” ์ง„ํ–‰ ํ•ฉ์˜๊ฐ€ ๋ช…์‹œ์ ์ด๊ณ  ๊ธฐํ•œ๋„ ์–ธ๊ธ‰๋จ. - \`์ง„ํ–‰๋ฏธ์ •\` โ€” ์ž‘์—…์ด ์–ธ๊ธ‰๋์œผ๋‚˜ ์‹ค์ œ๋กœ ์ง„ํ–‰ํ• ์ง€ ํ•ฉ์˜๊ฐ€ ๋ช…ํ™•ํ•˜์ง€ ์•Š์Œ. - \`๊ธฐํ•œ๋ฏธ์ •\` โ€” ํ•˜๊ธฐ๋กœ ํ™•์ •๋์œผ๋‚˜ ์™„๋ฃŒ์ผ/D-Day ๊ฐ€ ์ •ํ•ด์ง€์ง€ ์•Š์Œ. - \`์กฐ๊ฑด๋ถ€: <์„ ํ–‰์ž‘์—…>\` โ€” ๋‹ค๋ฅธ ์ž‘์—…ยท์‚ฌ๊ฑด์ด ๋๋‚˜์•ผ ์ง„ํ–‰ (์„ ํ–‰์ž‘์—…์„ ์งง๊ฒŒ ๋ช…์‹œ. ์˜ˆ: \`์กฐ๊ฑด๋ถ€: ๊ณ„์•ฝ ์ฒด๊ฒฐ ํ›„\`). - \`๋ฐ˜๋ณต: <์ฃผ๊ธฐ>\` โ€” ์ •๊ธฐ ๋ฐ˜๋ณต ์—…๋ฌด (์˜ˆ: \`๋ฐ˜๋ณต: ๋งค์ฃผ ๋ชฉ์š”์ผ\`). - ํ™•์‹ ์ด ์—†์œผ๋ฉด \`ํ™•์ •\`์ด ์•„๋‹ˆ๋ผ \`์ง„ํ–‰๋ฏธ์ •\`/\`๊ธฐํ•œ๋ฏธ์ •\` ์ชฝ์œผ๋กœ ๋ณด์ˆ˜์ ์œผ๋กœ ๋ถ„๋ฅ˜ํ•œ๋‹ค. + ์‹ค์ œ๋กœ ํ•ฉ์˜ยท๊ธฐํ•œ์ด ๋‚˜์™”์œผ๋ฉด \`ํ™•์ •\`์œผ๋กœ ์žก๊ณ , ์ •๋ง ์•ˆ ๋‚˜์˜จ ๊ฒƒ๋งŒ ๋ณด์ˆ˜์ ์œผ๋กœ \`์ง„ํ–‰๋ฏธ์ •\`/\`๊ธฐํ•œ๋ฏธ์ •\`์œผ๋กœ ๋‘”๋‹ค. + +## 5. ์˜คํ”ˆ ์ด์Šˆ +ํšŒ์˜ ์ข…๋ฃŒ ์‹œ์ ์— **์•„์ง ๊ฒฐ์ •๋˜์ง€ ์•Š์€ ๋ฏธ๊ฒฐ ์‚ฌํ•ญ**์„ ํ•œ๊ณณ์— ๋ชจ์•„ ๊ธ€๋จธ๋ฆฌํ‘œ๋กœ ์ •๋ฆฌํ•œ๋‹ค (๋„์ž… ์—ฌ๋ถ€ยท์ •์˜ ํ•„์š”ยทํ™•์ • ๋Œ€๊ธฐ ๋“ฑ). ๋ถ„์‚ฐ์‹œํ‚ค์ง€ ๋ง๊ณ  ์—ฌ๊ธฐ์„œ ํ•œ๋ˆˆ์— ๋ณด์ด๊ฒŒ ํ•œ๋‹ค. +- ์˜ˆ) Babylon.js ๋„์ž… ์—ฌ๋ถ€ / ์ตœ์†Œ ์ง€์› ์‚ฌ์–‘ ์ •์˜ / ๋ฐ์ดํ„ฐ ์ž…๋ ฅ ๊ตฌ์กฐ ํ™•์ • / ๊ฐ€์šฐ์‹œ์•ˆ ์Šคํ”Œ๋ž˜ํŒ… ๋„์ž… ์—ฌ๋ถ€ + +## 6. ๋ฆฌ์Šคํฌ ๋ฐ ๊ฒ€ํ†  ์‚ฌํ•ญ +ํ”„๋กœ์ ํŠธ ์ง„ํ–‰์— ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ๋Š” ์š”์†Œ๋ฅผ ํ‘œ๋กœ ์ •๋ฆฌํ•œ๋‹ค(์ผ์ • ์ง€์—ฐยท๊ฐœ๋ฐœ ๋‚œ์ด๋„ยท์ •์ฑ…/๋ณด์•ˆยท์™ธ๋ถ€ ์˜์กด์„ฑ ๋“ฑ). ์‹๋ณ„๋œ ๋ฆฌ์Šคํฌ๊ฐ€ ์—†์œผ๋ฉด "ํ˜„์žฌ ์‹๋ณ„๋œ ๋ฆฌ์Šคํฌ ์—†์Œ"์ด๋ผ๊ณ  ์ ๋Š”๋‹ค. ํ‘œ ์…€ ์•ˆ์—์„œ๋Š” ์ค„๋ฐ”๊ฟˆ๊ณผ \`|\` ๋ฌธ์ž๋ฅผ ์“ฐ์ง€ ๋ง ๊ฒƒ. +| ๋ฆฌ์Šคํฌ | ์˜ํ–ฅ | ๋Œ€์‘ ๋ฐฉ์•ˆ | +| --- | --- | --- | + +## 7. ๋…ผ์˜ ์‚ฌํ•ญ +๊ฒฐ์ •๋˜์ง€ ์•Š์•˜์œผ๋‚˜ ์˜ค๊ฐ„ ๋…ผ์˜๋ฅผ **์ฃผ์ œ๋ณ„ ๊ธ€๋จธ๋ฆฌํ‘œ**๋กœ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ •๋ฆฌํ•œ๋‹ค. (ํ˜„ํ™ฉ/ํ•ต์‹ฌ ๋…ผ์˜/์ถ”๊ฐ€ ๊ฒ€ํ†  ๊ฐ™์€ ํ•˜์œ„ ๊ตฌ์กฐ๋กœ ๊ธธ๊ฒŒ ๋Š˜์ด์ง€ ๋ง ๊ฒƒ.) ๋ฐœ์–ธ ๋‚˜์—ด ๊ธˆ์ง€, ๊ฒฐ๊ณผยท์Ÿ์  ์ค‘์‹ฌ. +**[์ฃผ์ œ๋ช…]** +- ํ•ต์‹ฌ ๋…ผ์  ํ•œ ์ค„ +- ํ•ต์‹ฌ ๋…ผ์  ํ•œ ์ค„ + +**[๋‹ค๋ฅธ ์ฃผ์ œ๋ช…]** +- ํ•ต์‹ฌ ๋…ผ์  ํ•œ ์ค„ + +# ์ตœ์ข… ์ ๊ฒ€ (์ถœ๋ ฅ ์ „ ๋‚ด๋ถ€ ํ™•์ธ โ€” ์ฒดํฌ ๋กœ๊ทธ๋Š” ์ถœ๋ ฅํ•˜์ง€ ๋ง ๊ฒƒ) +โ–ก ๊ฒฐ์ •์‚ฌํ•ญ์— ํ™•์ •๋œ ๊ฒƒ๋งŒ ์žˆ๋Š”๊ฐ€(๊ฒ€ํ†  ํ•„์š” ํ•ญ๋ชฉ์ด ์„ž์ด์ง€ ์•Š์•˜๋‚˜) โ–ก ์•ก์…˜ ์•„์ดํ…œ์— ๋‹ด๋‹น(๊ฐœ์ธ)ยท์ž‘์—…ยท๊ธฐํ•œยท์‚ฐ์ถœ๋ฌผ 4์š”์†Œ๊ฐ€ ์žˆ๋Š”๊ฐ€ โ–ก ์˜คํ”ˆ ์ด์Šˆ๊ฐ€ ํ•œ๊ณณ์— ๋ชจ์˜€๋Š”๊ฐ€ โ–ก ๋ฆฌ์Šคํฌ๊ฐ€ ์˜ํ–ฅยท๋Œ€์‘๋ฐฉ์•ˆ๊ณผ ํ•จ๊ป˜ ์ •๋ฆฌ๋๋Š”๊ฐ€ โ–ก ๋…ผ์˜์‚ฌํ•ญ์ด ์ฃผ์ œ๋ณ„ bullet๋กœ ๊ฐ„๊ฒฐํ•œ๊ฐ€ โ–ก ์š”์•ฝ์ด ๊ฒฐ๊ณผ ์ค‘์‹ฌ์ธ๊ฐ€ โ–ก ๊ฒฐ์ •ยท์•ก์…˜์— ๊ทผ๊ฑฐ ์ธ์šฉ์ด ๋ถ™์–ด ์žˆ๋Š”๊ฐ€ ์œ„ ํ˜•์‹์„ ์ •ํ™•ํžˆ ๋”ฐ๋ฅด๊ณ , ๋ชจ๋“  ๋‚ด์šฉ์€ ํ•œ๊ตญ์–ด๋กœ ์ž‘์„ฑํ•˜์‹œ์˜ค.`; @@ -132,6 +155,8 @@ ${segment} \`\`\` # ์ถœ๋ ฅ ํ˜•์‹ (์ด ์กฐ๊ฐ์— ํ•ด๋‹น ํ•ญ๋ชฉ์ด ์—†์œผ๋ฉด "์—†์Œ") +## ํšŒ์˜ ๋ชฉ์  +(์ด ์กฐ๊ฐ์—์„œ ํšŒ์˜์˜ ๋ชฉ์ ยท๋ฐฐ๊ฒฝ์ด ๋“œ๋Ÿฌ๋‚˜๋ฉด ํ•œ ์ค„๋กœ. ์•ˆ ๋“œ๋Ÿฌ๋‚˜๋ฉด "์—†์Œ") ## ๋ฐœ์–ธ์ž (์ด ์กฐ๊ฐ์— ๋“ฑ์žฅํ•œ ๋ฐœ์–ธ์ž ์ด๋ฆ„/ID ๋ชฉ๋ก) ## ์‚ฌ์‹ค(Fact) @@ -143,7 +168,7 @@ ${segment} ## ๋ฆฌ์Šคํฌ/์ด์Šˆ - ๋‚ด์šฉ โ€” ๊ทผ๊ฑฐ: "โ€ฆ" ## ์•ก์…˜(Action) -- [๋‹ด๋‹น] ์ž‘์—… ๋‚ด์šฉ (๊ธฐํ•œ: โ€ฆ) โ€” ๊ทผ๊ฑฐ: "โ€ฆ" +- [๋‹ด๋‹น(๊ฐœ์ธ ์šฐ์„ )] ์ž‘์—… ๋‚ด์šฉ (๊ธฐํ•œ: โ€ฆ / ์‚ฐ์ถœ๋ฌผ: โ€ฆ) โ€” ๊ทผ๊ฑฐ: "โ€ฆ" ## ์–ธ๊ธ‰๋œ ์ˆ˜์น˜ยท๋‚ ์งœยท๊ธˆ์•ก - ํ•ญ๋ชฉ: ๊ฐ’ โ€” ๊ทผ๊ฑฐ: "โ€ฆ"`; } diff --git a/src/features/datacollect/scheduling/calendarHelpers.ts b/src/features/datacollect/scheduling/calendarHelpers.ts index 694aeb5..477ee6a 100644 --- a/src/features/datacollect/scheduling/calendarHelpers.ts +++ b/src/features/datacollect/scheduling/calendarHelpers.ts @@ -31,9 +31,9 @@ export function toYmd(d: Date): string { return `${y}-${m}-${day}`; } -/** ํšŒ์˜๋ก ๋ณธ๋ฌธ์˜ "**๋‚ ์งœ**: 2026๋…„ 05์›” 08์ผ"์—์„œ ํšŒ์˜ ๋‚ ์งœ ์ถ”์ถœ. ์—†์œผ๋ฉด fallback. */ +/** ํšŒ์˜๋ก ๋ณธ๋ฌธ์˜ "**์ผ์‹œ**: 2026๋…„ 05์›” 08์ผ"(๊ตฌ ํ˜•์‹ "**๋‚ ์งœ**:" ๋„ ํ˜ธํ™˜)์—์„œ ํšŒ์˜ ๋‚ ์งœ ์ถ”์ถœ. ์—†์œผ๋ฉด fallback. */ export function extractMeetingDate(report: string, fallback: Date): Date { - const m = report.match(/๋‚ ์งœ\*{0,2}\s*[:๏ผš]\s*\*{0,2}\s*(\d{4})\s*๋…„\s*(\d{1,2})\s*์›”\s*(\d{1,2})\s*์ผ/); + const m = report.match(/(?:์ผ์‹œ|๋‚ ์งœ)\*{0,2}\s*[:๏ผš]\s*\*{0,2}\s*(\d{4})\s*๋…„\s*(\d{1,2})\s*์›”\s*(\d{1,2})\s*์ผ/); if (m) { const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3])); if (!isNaN(d.getTime())) return d; @@ -75,11 +75,11 @@ export function resolveTaskDate(due: string, meetingDate: Date, today: Date): { * 5์—ด ์‹ ํ‘œ(๋‹ด๋‹น | ์ž‘์—… ๋‚ด์šฉ | ์ž‘์—… ์ƒ์„ธ | ๊ธฐํ•œ | ์ƒํƒœ) ยท 4์—ด(์ƒํƒœ ์—†์Œ) ยท * ๊ตฌ(่ˆŠ) 3์—ด ํ‘œ(๋‹ด๋‹น | ์ž‘์—… ๋‚ด์šฉ | ๊ธฐํ•œ)๋ฅผ ๋ชจ๋‘ ์ง€์›ํ•œ๋‹ค. ๋ˆ„๋ฝ ์ปฌ๋Ÿผ์€ ๋นˆ ๋ฌธ์ž์—ด. */ -export function parseActionItems(report: string): { owner: string; work: string; detail: string; due: string; status: string }[] { - const rows: { owner: string; work: string; detail: string; due: string; status: string }[] = []; +export function parseActionItems(report: string): { owner: string; work: string; detail: string; deliverable: string; due: string; status: string }[] { + const rows: { owner: string; work: string; detail: string; deliverable: string; due: string; status: string }[] = []; let inSection = false; for (const line of report.split('\n')) { - if (/^#{1,6}\s*5\.\s*์•ก์…˜\s*์•„์ดํ…œ/.test(line)) { inSection = true; continue; } + if (/^#{1,6}\s*(?:\d+\.\s*)?์•ก์…˜\s*์•„์ดํ…œ/.test(line)) { inSection = true; continue; } if (!inSection) continue; if (/^#{1,6}\s/.test(line)) break; // ๋‹ค์Œ ์„น์…˜ ์‹œ์ž‘ โ†’ ์ข…๋ฃŒ if (!/^\s*\|/.test(line)) continue; @@ -87,12 +87,16 @@ export function parseActionItems(report: string): { owner: string; work: string; if (cells.length < 3) continue; if (/^:?-+:?$/.test(cells[0])) continue; // ํ‘œ ๊ตฌ๋ถ„์„  if (cells[0] === '๋‹ด๋‹น' || cells[1] === '์ž‘์—… ๋‚ด์šฉ') continue; // ํ—ค๋” - if (cells.length >= 5) { - rows.push({ owner: cells[0], work: cells[1], detail: cells[2], due: cells[3], status: cells[4] }); + if (cells.length >= 6) { + // ์‹  ํ˜•์‹: ๋‹ด๋‹น | ์ž‘์—… ๋‚ด์šฉ | ์ž‘์—… ์ƒ์„ธ | ์‚ฐ์ถœ๋ฌผ | ๊ธฐํ•œ | ์ƒํƒœ + rows.push({ owner: cells[0], work: cells[1], detail: cells[2], deliverable: cells[3], due: cells[4], status: cells[5] }); + } else if (cells.length === 5) { + // ๊ตฌ ํ˜•์‹: ๋‹ด๋‹น | ์ž‘์—… ๋‚ด์šฉ | ์ž‘์—… ์ƒ์„ธ | ๊ธฐํ•œ | ์ƒํƒœ (์‚ฐ์ถœ๋ฌผ ์ปฌ๋Ÿผ ์—†์Œ) + rows.push({ owner: cells[0], work: cells[1], detail: cells[2], deliverable: '', due: cells[3], status: cells[4] }); } else if (cells.length === 4) { - rows.push({ owner: cells[0], work: cells[1], detail: cells[2], due: cells[3], status: '' }); + rows.push({ owner: cells[0], work: cells[1], detail: cells[2], deliverable: '', due: cells[3], status: '' }); } else { - rows.push({ owner: cells[0], work: cells[1], detail: '', due: cells[2], status: '' }); + rows.push({ owner: cells[0], work: cells[1], detail: '', deliverable: '', due: cells[2], status: '' }); } } return rows; diff --git a/src/features/datacollect/scheduling/meetRegistration.ts b/src/features/datacollect/scheduling/meetRegistration.ts index cf999c6..7bdcacc 100644 --- a/src/features/datacollect/scheduling/meetRegistration.ts +++ b/src/features/datacollect/scheduling/meetRegistration.ts @@ -29,7 +29,7 @@ import { resolveTaskDate, toYmd, addBusinessDays } from './calendarHelpers'; import { logInfo } from '../../../utils'; // โ”€โ”€ ํƒ€์ž… โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -export type ActionRow = { owner: string; work: string; detail: string; due: string; status: string }; +export type ActionRow = { owner: string; work: string; detail: string; deliverable: string; due: string; status: string }; export type HoldKind = 'undecided' | 'nodate' | 'conditional'; export interface PendingItem { @@ -37,6 +37,7 @@ export interface PendingItem { owner: string; work: string; detail: string; + deliverable: string; // ์‚ฐ์ถœ๋ฌผ (์—†์œผ๋ฉด "ํ™•์ธ ํ•„์š”") due: string; kind: HoldKind; condition?: string; // kind=conditional ์˜ ์„ ํ–‰์ž‘์—… @@ -185,10 +186,12 @@ export async function registerAction( } /** ๋“ฑ๋ก ๋…ธํŠธ ๊ณตํ†ต ๋นŒ๋”. */ -export function buildNotes(p: { detail: string; meetTitle: string; owner: string; dueRaw: string; dateLabel: string; extra?: string[] }): string { +export function buildNotes(p: { detail: string; meetTitle: string; owner: string; deliverable?: string; dueRaw: string; dateLabel: string; extra?: string[] }): string { const detailLine = p.detail?.trim() || '(๋…น์ทจ๋ก์—์„œ ์ž‘์—… ์ƒ์„ธ๊ฐ€ ์ถ”์ถœ๋˜์ง€ ์•Š์Œ โ€” ํšŒ์˜๋ก ๋ณธ๋ฌธ ์ฐธ์กฐ)'; + const deliverable = p.deliverable?.trim(); return [ 'โ–  ์ž‘์—… ์ƒ์„ธ', detailLine, '', + ...(deliverable ? ['โ–  ์‚ฐ์ถœ๋ฌผ', deliverable, ''] : []), ...(p.extra && p.extra.length ? [...p.extra, ''] : []), 'โ–  ๋งฅ๋ฝ', `ยท ํšŒ์˜๋ก: ${p.meetTitle}`, @@ -325,7 +328,7 @@ export async function processConfirmDecisions( } const notes = buildNotes({ - detail: item.detail, meetTitle: pend.meetTitle, owner: item.owner, + detail: item.detail, meetTitle: pend.meetTitle, owner: item.owner, deliverable: item.deliverable, dueRaw: item.due, dateLabel: date || '(๋‚ ์งœ ์—†์Œ โ€” ์กฐ๊ฑด๋ถ€)', extra, }); const r = await registerAction(context, { diff --git a/tests/meetRegistration.test.ts b/tests/meetRegistration.test.ts index 4be115a..69c0bb4 100644 --- a/tests/meetRegistration.test.ts +++ b/tests/meetRegistration.test.ts @@ -4,10 +4,12 @@ * ๊ณผ๊ฑฐ ๋‚ ์งœ๋Š” ๋“ฑ๋กํ•˜๋˜ ์™„๋ฃŒํ™•์ธ ํ‘œ๊ธฐ, ๊ธฐํ•œ ํ•ด์„ ๋ถˆ๊ฐ€ ํ™•์ •๊ฑด์€ ๋ณด๋ฅ˜(์ถ”์ธก ๋“ฑ๋ก ๊ธˆ์ง€). */ import { classifyAction, parseConfirmArgs, normalizeDate, nextWeekday, taskKey } from '../src/features/datacollect/scheduling/meetRegistration'; +import { parseActionItems } from '../src/features/datacollect/scheduling/calendarHelpers'; +import { looksDegenerate } from '../src/features/datacollect/llm'; const MEET = new Date('2026-06-10'); const TODAY = new Date('2026-06-11'); -const row = (due: string, status: string) => ({ owner: '๋‚˜', work: 'ํ…Œ์ŠคํŠธ ์ž‘์—…', detail: '', due, status }); +const row = (due: string, status: string) => ({ owner: '๋‚˜', work: 'ํ…Œ์ŠคํŠธ ์ž‘์—…', detail: '', deliverable: '', due, status }); describe('classifyAction โ€” ๋“ฑ๋ก ๊ฒŒ์ดํŠธ ๋ถ„๊ธฐ', () => { test('ํ™•์ • + ๋ช…์‹œ ๊ธฐํ•œ โ†’ auto', () => { @@ -108,3 +110,51 @@ describe('๋ณด์กฐ ์œ ํ‹ธ', () => { expect(taskKey('DRM ๋ผ์ด์„ ์Šค ๊ฒ€ํ† ')).toBe(taskKey('drm ๋ผ์ด์„ ์Šค ๊ฒ€ํ† ')); }); }); + +describe('parseActionItems โ€” ์•ก์…˜ ํ‘œ ํŒŒ์‹ฑ (์‚ฐ์ถœ๋ฌผ ์ปฌ๋Ÿผ + ํ•˜์œ„ํ˜ธํ™˜)', () => { + test('์‹  ํ˜•์‹ 6์ปฌ๋Ÿผ: ๋‹ด๋‹น|์ž‘์—…|์ƒ์„ธ|์‚ฐ์ถœ๋ฌผ|๊ธฐํ•œ|์ƒํƒœ', () => { + const report = [ + '## 4. ์•ก์…˜ ์•„์ดํ…œ', + '| ๋‹ด๋‹น | ์ž‘์—… ๋‚ด์šฉ | ์ž‘์—… ์ƒ์„ธ | ์‚ฐ์ถœ๋ฌผ | ๊ธฐํ•œ | ์ƒํƒœ |', + '| --- | --- | --- | --- | --- | --- |', + '| ์†ก๋ณ‘์ค€ | ํ…Œ์ŠคํŠธ ์ƒ˜ํ”Œ 3์ข… ์„ ์ • | ํ›„๋ณด ๋น„๊ต | ํ…Œ์ŠคํŠธ URL ๋ชฉ๋ก | 6/18 | ํ™•์ • |', + '', + '## 5. ์˜คํ”ˆ ์ด์Šˆ', + ].join('\n'); + const rows = parseActionItems(report); + expect(rows).toHaveLength(1); + expect(rows[0]).toEqual({ owner: '์†ก๋ณ‘์ค€', work: 'ํ…Œ์ŠคํŠธ ์ƒ˜ํ”Œ 3์ข… ์„ ์ •', detail: 'ํ›„๋ณด ๋น„๊ต', deliverable: 'ํ…Œ์ŠคํŠธ URL ๋ชฉ๋ก', due: '6/18', status: 'ํ™•์ •' }); + }); + + test('๊ตฌ ํ˜•์‹ 5์ปฌ๋Ÿผ(์‚ฐ์ถœ๋ฌผ ์—†์Œ)๋„ ๊ทธ๋Œ€๋กœ ํŒŒ์‹ฑ โ€” deliverable ๋นˆ๊ฐ’', () => { + const report = [ + '## 5. ์•ก์…˜ ์•„์ดํ…œ', + '| ๋‹ด๋‹น | ์ž‘์—… ๋‚ด์šฉ | ์ž‘์—… ์ƒ์„ธ | ๊ธฐํ•œ | ์ƒํƒœ |', + '| --- | --- | --- | --- | --- |', + '| ๋‚˜ | DRM ๊ฒ€ํ†  | ์ƒ์„ธ | 6/20 | ํ™•์ • |', + ].join('\n'); + const rows = parseActionItems(report); + expect(rows).toHaveLength(1); + expect(rows[0]).toMatchObject({ owner: '๋‚˜', work: 'DRM ๊ฒ€ํ† ', deliverable: '', due: '6/20', status: 'ํ™•์ •' }); + }); + + test('์„น์…˜ ๋ฒˆํ˜ธ๊ฐ€ ๋ฐ”๋€Œ์–ด๋„(๋ฒˆํ˜ธ ๋ฌด๊ด€) ํƒ์ง€', () => { + const report = '## 9. ์•ก์…˜ ์•„์ดํ…œ\n| ๋‹ด๋‹น | ์ž‘์—… ๋‚ด์šฉ | ๊ธฐํ•œ |\n| --- | --- | --- |\n| ๋‚˜ | ์ž‘์—… | 6/20 |'; + expect(parseActionItems(report)).toHaveLength(1); + }); +}); + +describe('looksDegenerate โ€” ๋ชจ๋ธ ์ถœ๋ ฅ ๋ถ•๊ดด ๊ฐ์ง€', () => { + test('์ •์ƒ ํšŒ์˜๋ก์€ ํ†ต๊ณผ', () => { + expect(looksDegenerate('## ๊ฒฐ์ • ์‚ฌํ•ญ\n- ํ…Œ์ŠคํŠธ ์ƒ˜ํ”Œ 3์ข… ์„ ์ • โ€” ๊ทผ๊ฑฐ: "์ƒ˜ํ”Œ 3๊ฐœ๋กœ ๊ฐ€์ž"')).toBe(false); + }); + test('๊ตฌ์ ˆ ๋ฐ˜๋ณต ๋ฃจํ”„ ๊ฐ์ง€', () => { + expect(looksDegenerate('์„œ๋น„์Šค ๊ธฐํ”„ํŠธ ์„œ๋น„์Šค ๊ธฐํ”„ํŠธ ์„œ๋น„์Šค ๊ธฐํ”„ํŠธ ์„œ๋น„์Šค ๊ธฐํ”„ํŠธ ์„œ๋น„์Šค ๊ธฐํ”„ํŠธ')).toBe(true); + }); + test('๋Œ€์ฒด๋ฌธ์ž(๊นจ์ง) ๊ฐ์ง€', () => { + expect(looksDegenerate('์ •์ƒ ํ…์ŠคํŠธ ๏ฟฝ๏ฟฝ ๋‹ค๋žตํ•œ์„œ ๋Ÿ‰ํ•œ์„œ')).toBe(true); + }); + test('ํ‘œ ๊ตฌ๋ถ„์„  ๋ฐ˜๋ณต์€ ์˜คํƒ ์•„๋‹˜', () => { + expect(looksDegenerate('| --- | --- | --- | --- | --- | --- |')).toBe(false); + }); +});