v2.2.265: /meet 검수 루프·편집·액션 상세·논의 매듭

/meet 파이프라인 대폭 개선:
- 검수 루프: 회의록 검증 후 지적분만 수정, 통과까지 반복 (전체 재작성 X)
- 편집(Editor): 검수 통과 후 보고서 형태로 구조 정리 (환각 방지 재검증)
- 액션 상세 확장: task 노트에 2~3문장 자기완결 설명 자동 생성
- 주요 논의/쟁점: 각 쟁점을 논점→논의→결론으로 그 자리에서 매듭
- 자동 등록 0건 fix: 기한 없는 확정·기한미정을 작성일(오늘)로 등록 (폴백)

신규 설정:
- g1nation.meetVerifyMaxRounds (기본 2)
- g1nation.meetEditorPass (기본 ON)
- g1nation.meetTaskDateFallback (기본 today)
- g1nation.meetTaskDetailExpand (기본 ON)

신규 프롬프트:
- buildMeetRevisePrompt (지적분만 수정)
- buildMeetEditorPrompt (근거 유지 편집)
- buildTaskDetailPrompt (액션 상세 확장)

신규 파서:
- parseTaskDetails (task 상세 맵핑)

신규 테스트:
- classifyAction dateFallback 동작 5건
- parseTaskDetails 파싱 4건

tsc ✓ · jest 715/715 ✓

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 18:22:52 +09:00
parent 40d003635e
commit 8f1ce4d617
6 changed files with 392 additions and 51 deletions
+105 -7
View File
@@ -92,11 +92,12 @@ ${OUTPUT_FORMAT}`;
/** 최종 회의록 출력 형식 — 단일샷(buildMeetPrompt)과 병합 단계(buildMeetReducePrompt)가 공유. */
const OUTPUT_FORMAT = `# 작성 원칙 (회의록 품질 — 출력 전 반드시 내재화)
회의록은 *대화 요약 문서*가 아니라 **프로젝트 관리 문서**다. "무엇이 결정됐는가 / 누가(어느 팀이) 무엇을 해야 하는가 / 무엇이 아직 미결인가"를 우선한다.
- **완전성 (누락 금지 — 최우선)**: 실제로 논의·결정·요청·질문된 항목은 **하나도 빠뜨리지 않는다.** '핵심 요약'만 3~5줄 다이제스트로 압축하고, **'주요 논의/결정/액션/오픈이슈' 본문 섹션은 다뤄진 모든 안건을 담아 상세히** 쓴다. 개조식·군더더기 최소화는 *문체*일 뿐 *내용 삭제가 아니다* — 각 항목은 수치·조건·대상·기한 등 핵심 디테일을 넣어 **그 한 줄만 봐도 이해되게(자기완결)** 적는다(맥락 없는 앙상한 뼈대 금지). 섹션이 길어지는 것은 정상이다.
- **개조식·명사형 종결 (필수 문체)**: 모든 항목을 개조식으로 — 명사형 또는 '~함·됨·필요·예정·합의·완료·확인' 류로 끝맺는다. **서술형 종결어미('~하였다/했다/합니다/입니다/됩니다/할 예정이다/것으로 보인다') 금지** — 회의록은 일기·기사·서간문이 아니다. 한 항목 = 한 줄, 군더더기·접속사("따라서/그리고/하였으며") 최소화.
- 좋은 예: \`- CDN 경로는 위버스 운영툴로 관리 — 합의\` / \`- 보안 솔루션 2개사 PoC·비용 검토 완료, 최종 도입안 미정\`
- 나쁜 예: \`- CDN 경로 정보를 위버스 운영툴을 통해 관리하는 방식으로 합의하였습니다.\` / \`- …검토를 완료하였으며, 향후 …결정할 예정이다.\`
- **이슈 → 결정 흐름 (구조 순서)**: 독자가 "무슨 쟁점이 있었고 → 어떤 결정이 났는지"를 따라가도록, '주요 논의 / 쟁점'을 '결정 사항' **앞**에 둔다. **쟁점(질문·대립·선택지)은 '주요 논의'에, 결론(답)은 '결정 사항'에** 나눠 적되 — 같은 *결론*을 양쪽에 반복하지 말 것(쟁점 ≠ 결론, 중복 아님).
- **한 사실은 한 섹션에만**: 같은 항목을 요약·결정·액션에 복 기재하지 말 것. 핵심 요약은 미리보기가 아니라 다이제스트다.
- **이슈 → 결정 흐름 (구조 순서)**: 독자가 "무슨 쟁점이 있었고 → 어떻게 논의됐고 → 어떻게 됐는지"를 따라가도록, '주요 논의 / 쟁점'을 '결정 사항' **앞**에 둔다. **'주요 논의'는 각 주제를 논점→논의→결론으로 그 자리에서 매듭짓고**(결론은 *상태 한 줄* — 미결/조건부/보류거나, 확정이면 짧게), **'결정 사항'은 그중 확정 결정만** 정본으로 다시 크고 명확하게 모은다. 즉 주요 논의의 '결론'은 *상태 요약*, 결정 사항은 *확정 결정의 정본* — 성격이 달라 상태 수준 겹침은 허용하되, **같은 문장을 통째로 복붙하지 말 것**.
- **한 사실은 한 섹션에만**: 같은 항목을 통째로 여러 섹션에 복 기재하지 말 것(예외: 위처럼 주요 논의 '결론' 상태 한 줄 ↔ 결정 사항 정본은 허용). 핵심 요약은 미리보기가 아니라 다이제스트다.
- **결정 ↔ 액션 경계**: 담당+행동이 있는 *일감*은 '액션 아이템' 표로 보낸다. '결정 사항'에는 **담당자 없는 순수 방향/정책 결정만** 남긴다(예: "제품 리스트 클릭 이벤트 제거하기로 함"). "구현하기로 함"처럼 누가 할 일이면 액션이다.
- **결정 ≠ 검토요청**: "테스트해보고 결정 / 다시 보고 얘기 / 고민해보자"는 결정이 아니다 → 오픈 이슈. 명시적 합의만 결정.
- **담당은 팀/역할 우선**: STT 화자번호 매핑이 불확실하므로 개인명보다 **팀/역할**(예: 넥서스개발팀, 기획, UI)로 적는다. 개인이 명단+문맥으로 확실할 때만 병기. "참석자 N" 금지.
@@ -124,9 +125,11 @@ const OUTPUT_FORMAT = `# 작성 원칙 (회의록 품질 — 출력 전 반드
- 예) 보안 솔루션 2개사(라인컴퍼니·스틸리언) PoC·비용 검토 완료, 최종 도입안·일정 미정
## 주요 논의 / 쟁점
**결정에 앞서**, 회의에서 다뤄진 안건·쟁점을 **주제별**로 먼저 제시한다(독자가 "무슨 이슈가 있었고 → 어떤 결정이 났는지" 흐름을 따라가게). 발언 나열이 아니라 **쟁점·대립·선택지 중심** 개조식. 결론 자체는 여기 쓰지 말고 '결정 사항'으로 — 여기엔 *질문/논거/미해결*만.논점 끝 [mm:ss].
**결정에 앞서**, 회의에서 다뤄진 안건을 **주제별**로, 각 주제가 **그 자리에서 매듭지어 보이게 "논점 → 논의 → 결론" 3단**으로 적는다(독자가 이 섹션만 읽어도 "무슨 이슈였고 → 어떻게 오갔고 → 그래서 어떻게 됐는지"를 따라가게). 발언을 그대로 옮기지 말고 **요지로 압축**, 개조식·명사형 종결, 끝 [mm:ss].
**[주제명]**
- 핵심 쟁점/논거 한 줄 (명사형 종결) — [mm:ss]
- **논점**: 핵심 쟁점/질문 한 줄 (명사형 종결) — [mm:ss]
- **논의**: 실제 오간 주장·근거·선택지·대립의 요지 (녹취/노트에 있는 내용만 — 없으면 이 줄 생략, 지어내지 말 것) — [mm:ss]
- **결론**: *그래서 어떻게 됐는지* 한 줄로 매듭 — 다음 중 하나로 표기: \`합의·결정됨\`(핵심 결정이면 아래 '결정 사항'에도 정본으로 크게 표기) / \`미결 — 오픈 이슈로\` / \`조건부: <선행>\` / \`보류·다음 회의로\`. **결정 안 났어도 반드시 이 줄로 현재 상태를 남긴다**(붕 뜨는 쟁점 금지) — [mm:ss]
## 결정 사항
위 쟁점에 대해 **명시적으로 합의·확정된 순수 방향/정책 결정만** 적는다(담당 있는 일감은 액션으로). 개조식·명사형 종결, 각 줄 끝 타임스탬프.
@@ -159,7 +162,7 @@ const OUTPUT_FORMAT = `# 작성 원칙 (회의록 품질 — 출력 전 반드
| --- | --- | --- | --- |
# 최종 점검 (출력 전 내부 확인 — 체크 로그는 출력하지 말 것)
□ 모든 문장이 개조식·명사형 종결(서술형 '~하였다/했습니다/할 예정이다' 0개) □ '주요 논의/쟁점'이 '결정 사항'보다 앞에 위치 □ 참석자 명단에 "(참석자 N)" 병기 0개 □ "참석자 N" 토큰 0개 □ 화자가 팀/역할로 귀속(개인 추측 없음) □ 결정사항에 순수 방향/정책만(일감은 액션으로) □ "테스트후결정·검토필요"가 결정에 섞이지 않음 □ 즉시 반박된 가설을 이슈로 격상 안 함 □ 화자간 충돌은 단일값 확정 말고 그대로 표기 □ 같은 사실이 여러 섹션에 복 안 □ 빈 칸은 "—"(추측 채움 없음) □ 통째 미정 항목은 오픈이슈로 □ 액션 상태가 5종 taxonomy 형식 □ 리스크는 실제 논의됐을 때만 표 띄움 □ 모든 결정·액션·논점에 [mm:ss]
□ 모든 문장이 개조식·명사형 종결(서술형 '~하였다/했습니다/할 예정이다' 0개) □ '주요 논의/쟁점'이 '결정 사항'보다 앞에 위치 □ 주요 논의 각 주제가 논점→논의→결론으로 매듭(결론 한 줄 없이 붕 뜬 쟁점 0개) □ 참석자 명단에 "(참석자 N)" 병기 0개 □ "참석자 N" 토큰 0개 □ 화자가 팀/역할로 귀속(개인 추측 없음) □ 결정사항에 순수 방향/정책만(일감은 액션으로) □ "테스트후결정·검토필요"가 결정에 섞이지 않음 □ 즉시 반박된 가설을 이슈로 격상 안 함 □ 화자간 충돌은 단일값 확정 말고 그대로 표기 □ 같은 문장을 통째로 여러 섹션에 복함(주요논의 결론↔결정 정본의 상태 수준 겹침은 예외) □ 빈 칸은 "—"(추측 채움 없음) □ 통째 미정 항목은 오픈이슈로 □ 액션 상태가 5종 taxonomy 형식 □ 리스크는 실제 논의됐을 때만 표 띄움 □ 모든 결정·액션·논점에 [mm:ss]
위 형식을 정확히 따르고, 모든 내용은 한국어로 작성하시오.`;
@@ -227,11 +230,12 @@ export function buildMeetReducePrompt(notes: string, metadata: string): string {
- **화자 역할 통일**: 각 조각의 '화자 역할 매핑'을 종합해 같은 팀/역할 라벨로 통일한다. 최종 문서에 "참석자 N"이 단 하나도 남으면 안 된다(0개). 조각마다 매핑이 엇갈리면 다수·문맥으로 결정하고, 끝내 불명이면 주체 없이 중립 서술.
- **전역 헤드라인 추출**: 조각 전체(특히 마지막 조각)를 관통하는 **결론·핵심 요구**를 찾아 '핵심 요약' 맨 앞에 세운다. 한 청크에만 있던 핵심 요구가 묻히지 않게 한다.
- **담화 상태 반영**: \`(반박됨)\`·\`(철회됨)\`으로 표시된 가설은 오픈 이슈·결정으로 격상하지 말 것. \`(미합의)\`는 오픈 이슈/논의로. \`(합의)\`만 결정 후보.
- **주요 논의 = 논점→논의→결론**: 각 주제에 대해 노트의 '논의(Discussion)'·'리스크/이슈'를 *논의* 줄로, '결정'·담화상태(합의/미합의/보류)를 *결론* 줄로 엮어 논점→논의→결론을 완성한다. **결정이 없던 쟁점도 '미결·보류' 결론을 반드시 달아** 붕 뜨지 않게 한다(결론 줄 없는 쟁점 금지).
- **결정/액션 경계**: 담당+행동이 있는 일감은 액션 표로, 결정엔 순수 방향/정책만.
- **의미 dedup**: 표현이 달라도 같은 작업·논점은 **하나로 병합**한다(여러 조각에서 중복 추출된 것). 한 사실은 한 섹션에만.
- **충돌 보존**: 수치·의견이 화자간 갈리면 단일값으로 확정하지 말고 "A vs B, 미정"으로 오픈 이슈에 적는다.
- **빈 칸은 "—"**, 모든 칸이 미정인 액션은 오픈 이슈로. 리스크는 실제 논의된 경우만 표를 만든다.
- **안건 누락 점검**: 노트의 '안건/주제' 목록을 체크리스트 삼아, 다뤄진 의제가 결과 문서에서 누락되지 않았는지 확인한다(특히 회의 도입부 첫 안건).
- **안건 누락 점검 (필수)**: 노트의 '안건/주제' 목록을 체크리스트 삼아, 다뤄진 의제·사실·결정·액션을 **하나도 빠뜨리지 말고** 결과 문서에 반영한다(중복만 병합, 특히 회의 도입부 첫 안건). 출력이 길어지더라도 **항목을 잘라내 분량을 맞추지 말 것** — 완전성이 간결성보다 우선.
- 메타데이터와 노트가 충돌하면 메타데이터 우선.
[메타데이터]
@@ -255,7 +259,7 @@ export function buildMeetVerifyPrompt(report: string, source: string): string {
# 점검 항목
1. **근거 미확인**: '결정 사항'·'액션 아이템'의 내용(및 수치·날짜)이 근거 소스에서 확인되는가? 못 찾으면 FLAG. (표기·철자 차이는 무시, 의미로 대조.)
2. **화자번호 잔존**: 회의록 본문에 "참석자 N"/"화자 N"/"발언자 A" 같은 STT 화자번호가 남아 있는가? 있으면 FLAG.
3. **결정 과확정**: '결정 사항'에 든 항목이 소스에서는 "테스트 후 결정 / 다시 보고 얘기 / 고민해보자"처럼 *조건부·미합의*인가? 그렇다면 FLAG(오픈 이슈로 내려야 함).
3. **결정 과확정**: '결정 사항'(또는 '주요 논의'의 **결론** 줄에 \`합의·결정됨\`으로 적힌 것)이 소스에서는 "테스트 후 결정 / 다시 보고 얘기 / 고민해보자"처럼 *조건부·미합의*인가? 그렇다면 FLAG(결론을 '미결'로 내려야 함).
4. **폐기 가설 격상**: 소스에서 즉시 반박·철회된 발언이 회의록의 오픈이슈/리스크/결정으로 격상돼 있는가? 있으면 FLAG.
5. **액션 중복**: 액션 아이템 중 의미가 사실상 동일한데 별개 행으로 중복된 것이 있는가? 있으면 FLAG(병합 필요).
@@ -275,3 +279,97 @@ ${report}
${source}
\`\`\``;
}
/**
* [검수 수정] 검증자가 지적한 부분만 고쳐 '수정된 전체 회의록'을 반환한다.
* 핵심: 지적되지 않은 내용은 그대로 보존(재작성 금지), 소스에 없는 사실 추가 금지.
*/
export function buildMeetRevisePrompt(report: string, flagged: string, source: string): string {
return `# 임무
아래 [회의록]에서 [검수 지적사항]에 해당하는 부분**만** 고쳐, 수정된 **전체 회의록**을 출력하라.
# 절대 규칙
- **지적된 부분만** 수정한다. 지적되지 않은 문장·표·섹션·표현은 **한 글자도 바꾸지 말고 그대로** 둔다(전체 재작성·재요약 금지).
- 수정은 [근거 소스]에 맞춘다. 소스에 **없는 새 사실·수치·날짜·항목을 만들어 넣지 말 것**.
- 지적 유형별 처리:
- **[근거]** 소스에서 확인되면 근거 표기만 보강, 확인 안 되면 그 항목을 삭제하거나 '오픈 이슈'로 강등.
- **[화자번호]** "참석자 N"/"화자 N"/"발언자 A"를 팀/역할로 치환(불명이면 주체 없이 중립 서술).
- **[과확정]** '결정 사항'에서 빼서 '오픈 이슈'로 이동.
- **[폐기가설]** 반박·철회된 항목을 결정/오픈이슈/리스크에서 제거.
- **[중복]** 의미가 같은 액션 행을 하나로 병합.
- 출력은 수정된 **회의록 본문 하나**뿐. 머리말·설명·코드펜스(\`\`\`) 금지.
[검수 지적사항]
${flagged}
[근거 소스]
\`\`\`
${source}
\`\`\`
[회의록]
${report}`;
}
/**
* [편집(Editor)] 검수를 통과한 회의록을 보고서 형태로 구조·가독성만 다듬는다.
* 편집자는 작성자가 아니다 — 소스에 없는 사실을 새로 만들지 않고, 기존 항목을 삭제·축약하지 않는다.
*/
export function buildMeetEditorPrompt(report: string, source: string): string {
return `# 임무
아래 [회의록]을 최종 **보고서** 형태로 다듬어 출력하라. 너는 편집자이며 작성자가 아니다.
# 절대 규칙 (환각·누락 금지)
- **형식·구조·가독성만** 개선한다: 섹션 순서 정돈, 표 정렬, 개조식·명사형 종결 통일, 어색한 문장 다듬기.
- [근거 소스]에 **없는 사실·수치·날짜·이름·항목을 절대 새로 만들지 말 것**(편집 ≠ 창작).
- 기존 결정·액션·오픈이슈·논의 항목을 **임의로 삭제·병합·축약하지 말 것** — 모든 정보를 보존한다(완전성 우선).
- 화자번호("참석자 N" 등)가 남아 있으면 팀/역할로 정리한다.
- 기존 섹션 구조(회의 개요 / 핵심 요약 / 주요 논의·쟁점 / 결정 사항 / 액션 아이템(표) / 오픈 이슈 / 리스크)를 **유지**한다. 없는 섹션을 억지로 만들지 않는다.
- 출력은 다듬어진 **보고서 본문 하나**뿐. 머리말·설명·코드펜스(\`\`\`) 금지.
[근거 소스]
\`\`\`
${source}
\`\`\`
[회의록]
${report}`;
}
/**
* [액션 상세 확장 — task 노트용] 슬림 액션 표에는 '작업 상세'가 없어 Google Task 노트가
* 앙상해진다. 이 프롬프트는 각 액션을 '회의 불참자도 무엇을 해야 하는지 이해할' 자기완결
* 설명으로 풀어 쓴다(근거 소스 기반, 환각 금지). 번호는 입력과 1:1로 매핑해 파싱한다.
*/
export function buildTaskDetailPrompt(
actions: { owner: string; work: string; due: string; source: string }[],
meetTitle: string,
source: string,
): string {
const list = actions
.map((a, i) => `[${i + 1}] (담당: ${a.owner || '미지정'} · 기한: ${a.due || '미정'} · 근거: ${a.source || '—'}) ${a.work}`)
.join('\n');
return `# 임무
아래 [액션 목록]의 각 항목을, 이 회의에 **참석하지 않은 담당자도 무엇을 해야 하는지 이해**할 수 있게 풀어서 설명하라. 각 항목마다 2~3문장.
# 규칙
- 각 설명에 담을 것(가능한 범위에서): **무엇을** 하는 작업인지 / **왜**(배경·목적) / **기대 결과물·완료 기준**.
- **[근거 소스]에서 확인되는 내용만** 쓴다. 소스에 없는 수치·인물·마감·결과를 **지어내지 말 것**. 정보가 부족하면 액션 문장을 더 명확히 풀어 쓰는 선에서 멈춘다(억지 창작 금지).
- 액션 제목을 그대로 반복하지 말고 실질 설명을 준다. 회의 화자번호("참석자 N")를 쓰지 말고 팀/역할로.
- 각 설명은 **한 문단(줄바꿈 없이)**.
# 출력 형식 (정확히 이 형식, 번호는 입력과 1:1 대응)
[1] <설명 한 문단>
[2] <설명 한 문단>
[회의] ${meetTitle}
[액션 목록]
${list}
[근거 소스]
\`\`\`
${source}
\`\`\``;
}