export const meta = { name: 'generate-proposal', description: 'Tổng hợp tài liệu SAD thành proposal khách hàng dạng HTML/CSS: content (nội dung thương mại) → build (HTML) → review (rò rỉ nội bộ, trung thực, chất lượng HTML). Mỗi lần gọi chạy 1 stage (args.stage) có gate; "all" chạy liền kèm 1 vòng tự sửa theo review.', whenToUse: 'Gọi từ skill sad-proposal sau khi SAD đã hoàn thiện (docs/SAD.md) và đã có docs/proposal/proposal-config.md.', phases: [ { title: 'Content', detail: 'proposal-writer: SAD + config → proposal-content.md' }, { title: 'Build', detail: 'proposal-builder: content → index.html + artifact.html' }, { title: 'Review', detail: 'proposal-reviewer: rò rỉ nội bộ, trung thực, chất lượng HTML' }, ], } // --------------------------------------------------------------------------- // Tham số // stage: content | build | review | all (mặc định: all) // notes: ghi chú người duyệt khi chạy lại 1 stage (Revise) // keyFacts: danh sách {fact, source} từ gate content, truyền cho stage review // autoFix: (all) tự sửa 1 vòng theo review — mặc định true // sadFile / sectionsDir / briefFile / configFile / outDir: đường dẫn // --------------------------------------------------------------------------- const a = args && typeof args === 'object' ? args : {} const STAGES = ['content', 'build', 'review'] const stage = a.stage || 'all' if (stage !== 'all' && !STAGES.includes(stage)) { throw new Error(`args.stage không hợp lệ: "${stage}". Hợp lệ: all | ${STAGES.join(' | ')}`) } const SAD = a.sadFile || 'docs/SAD.md' const S = a.sectionsDir || 'docs/sections' const BRIEF = a.briefFile || 'docs/00-project-brief.md' const CFG = a.configFile || 'docs/proposal/proposal-config.md' const OUT = a.outDir || 'docs/proposal' const CONTENT = `${OUT}/proposal-content.md` const HTML = `${OUT}/index.html` const ART = `${OUT}/artifact.html` const autoFix = a.autoFix !== false const notes = stage !== 'all' && a.notes ? `\n\n## Ghi chú từ người duyệt (bắt buộc xử lý trước khi làm gì khác)\n${a.notes}\n` : '' const run = (s) => stage === 'all' || stage === s // --------------------------------------------------------------------------- // Schema — mọi agent trả structured output để script rẽ nhánh bằng code // --------------------------------------------------------------------------- const CONTENT_SCHEMA = { type: 'object', properties: { filesWritten: { type: 'array', items: { type: 'string' } }, sections: { type: 'array', items: { type: 'object', properties: { id: { type: 'string' }, title: { type: 'string' }, sourceSadSections: { type: 'array', items: { type: 'string' } } }, required: ['id', 'title'], }, }, placeholders: { type: 'array', items: { type: 'object', properties: { id: { type: 'string' }, description: { type: 'string' }, section: { type: 'string' } }, required: ['description'], }, }, excludedInternal: { type: 'array', items: { type: 'string' } }, keyFacts: { type: 'array', items: { type: 'object', properties: { fact: { type: 'string' }, source: { type: 'string' } }, required: ['fact', 'source'] }, }, confidence: { type: 'string', enum: ['high', 'medium', 'low'] }, summary: { type: 'string' }, }, required: ['filesWritten', 'sections', 'placeholders', 'confidence', 'summary'], } const BUILD_SCHEMA = { type: 'object', properties: { filesWritten: { type: 'array', items: { type: 'string' } }, sectionsRendered: { type: 'array', items: { type: 'string' } }, placeholdersCount: { type: 'number' }, mermaidBlocks: { type: 'number' }, approxSizeKB: { type: 'number' }, checks: { type: 'object', properties: { standaloneDoc: { type: 'boolean' }, artifactVariant: { type: 'boolean' }, title: { type: 'boolean' }, tocAnchors: { type: 'boolean' }, themeTokens: { type: 'boolean' }, printCss: { type: 'boolean' }, responsiveTables: { type: 'boolean' }, mermaidLoaderGuarded: { type: 'boolean' }, }, }, confidence: { type: 'string', enum: ['high', 'medium', 'low'] }, summary: { type: 'string' }, }, required: ['filesWritten', 'checks', 'confidence', 'summary'], } const REVIEW_SCHEMA = { type: 'object', properties: { verdict: { type: 'string', enum: ['pass', 'revise'] }, leaks: { type: 'array', items: { type: 'object', properties: { file: { type: 'string' }, snippet: { type: 'string' }, why: { type: 'string' } }, required: ['file', 'snippet'] }, }, placeholders: { type: 'array', items: { type: 'string' } }, findings: { type: 'array', items: { type: 'object', properties: { target: { type: 'string', enum: ['content', 'html'] }, issue: { type: 'string' }, suggestion: { type: 'string' }, severity: { type: 'string', enum: ['high', 'medium', 'low'] }, }, required: ['target', 'issue', 'severity'], }, }, factChecks: { type: 'array', items: { type: 'object', properties: { claim: { type: 'string' }, sadEvidence: { type: 'string' }, ok: { type: 'boolean' } }, required: ['claim', 'ok'] }, }, summary: { type: 'string' }, }, required: ['verdict', 'leaks', 'findings', 'summary'], } // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- const sources = `Nguồn: ${CFG} (thông tin thương mại — nguồn duy nhất cho giá/ngày/tên người), ` + `${SAD} (nếu chưa có thì đọc ${BRIEF} + toàn bộ ${S}/01–09).` const fmtFindings = (list) => list.map((f, i) => `${i + 1}. [${f.severity}] ${f.issue}${f.suggestion ? ` → ${f.suggestion}` : ''}`).join('\n') function writeContent(extra) { return agent( `${sources}\nSoạn nội dung proposal khách hàng theo đúng cấu trúc và marker trong system prompt của bạn, ghi ra ${CONTENT}. ` + `Tuyệt đối không đưa nội dung nội bộ (ghi chú rà soát, trạng thái duyệt, câu hỏi mở, findings, mã FR ngoài Phụ lục) vào proposal. ` + `Không bịa giá/ngày/số liệu — dùng [[CẦN ĐIỀN: ...]] và liệt kê trong placeholders.${extra || ''}`, { agentType: 'proposal-writer', label: 'proposal-writer', phase: 'Content', schema: CONTENT_SCHEMA }, ) } function buildHtml(extra) { return agent( `Đọc ${CONTENT} và ${CFG} (brandColor, language, logo nếu có). Dựng ${HTML} (tài liệu HTML độc lập hoàn chỉnh) ` + `và ${ART} (biến thể Artifact: không doctype/html/head/body, bắt đầu bằng