export const meta = { name: 'sa-pipeline', description: 'Điều phối quy trình Solution Architect (sa-1…sa-4) từng bước có người duyệt: mỗi lần gọi chạy đúng MỘT stage/activity (--focus), hoặc audit (chấm AG1–AG4 + DTM/ADL, chỉ đọc), sign (ghi chữ ký duyệt của con người), sync (INDEX/ADL). Không có chế độ chạy liền.', whenToUse: 'Gọi từ skill sa-pipeline. Bắt buộc args.project, args.stage, args.date.', phases: [ { title: 'Init', detail: 'sa-lifecycle: cấu trúc sa-output + INDEX' }, { title: 'Audit', detail: 'sa-gate-auditor: chấm AG1–AG4, Confidence, DTM/ADL, đồng bộ BA (chỉ đọc)' }, { title: 'Context', detail: 'sa-1: driver, ràng buộc, as-is, phương án, TCO, rủi ro' }, { title: 'Architecture', detail: 'sa-2: QAS, ASR, SAD, ICD, DAT, SEC, INF, FAIL, ADR' }, { title: 'Enablement', detail: 'sa-3: guideline, fitness function, design review, tech debt' }, { title: 'Evolution', detail: 'sa-4: conformance, chi phí, drift, post-mortem, roadmap' }, { title: 'Sign', detail: 'Ghi quyết định duyệt/baseline/revise của con người' }, { title: 'Sync', detail: 'Cập nhật INDEX/ADL từ header thật' }, ], } const TRACK = { label: 'SA', outputRoot: 'sa-output', runner: 'sa-stage-runner', auditor: 'sa-gate-auditor', lifecycleSkill: 'sa-lifecycle', traceSkill: 'sa-conformance', indexDir: '00-index', stages: { context: { phase: 'Context', skill: 'sa-1-context', dir: '01-context', gate: 'AG1', prevGate: null, activities: ['drivers', 'constraints', 'as-is', 'options', 'cost-risk'], externalPrereq: 'BA đã qua G1 (có BRIEF/GOAL/RQ)' }, architecture: { phase: 'Architecture', skill: 'sa-2-architecture', dir: '02-architecture', gate: 'AG2', prevGate: 'AG1', activities: ['qas', 'asr', 'sad', 'icd', 'dat', 'sec', 'inf', 'fail', 'adr'], orderedPrefix: ['qas', 'asr', 'sad'] }, enablement: { phase: 'Enablement', skill: 'sa-3-enablement', dir: '03-enablement', gate: 'AG3', prevGate: 'AG2', activities: ['agd', 'fit', 'review', 'debt'] }, evolution: { phase: 'Evolution', skill: 'sa-4-evolution', dir: '04-evolution', gate: 'AG4', prevGate: 'AG3', activities: ['conformance', 'cost', 'drift', 'postmortem', 'review', 'roadmap'] }, }, gates: { AG1: 'PO + Tech Lead', AG2: 'Tech Lead + Security + Ops/SRE', AG3: 'Tech Lead + QA + SRE', AG4: 'PO + SRE + Enterprise Architect', }, profileFields: [], } // =========================================================================== // ENGINE (dùng chung cho ba-pipeline / sa-pipeline) — một stage mỗi lần gọi // project (bắt buộc) · stage (bắt buộc) · date YYYY-MM-DD (bắt buộc) // activity : một hoạt động trong stage (mặc định: cả stage, theo thứ tự) // scope : phạm vi (VD US-012,US-013 · CR-004 · --focus) // inputs[] : file/nguồn người dùng cung cấp (ưu tiên cao nhất) // answers : trả lời của người dùng cho OQ / humanInputNeeded // notes : ghi chú người duyệt (Revise) // override : true = người dùng khẳng định chạy dù gate trước chưa qua // profile : {product, lifecycle, rigor} (init, hoặc khi đã xác nhận) // approvals[] : (sign) {artifact, decision: approve|baseline|revise, approver, role, note} // gate, decisions[] : (sign) gate được ký, DEC cần ghi // outputRoot : mặc định TRACK.outputRoot // =========================================================================== const a = args && typeof args === 'object' ? args : {} const STAGE_NAMES = ['init', 'audit', ...Object.keys(TRACK.stages), 'sign', 'sync'] if (!a.project) throw new Error('Thiếu args.project — tên project (một project mỗi lần chạy)') if (!STAGE_NAMES.includes(a.stage)) throw new Error(`args.stage không hợp lệ: "${a.stage}". Hợp lệ: ${STAGE_NAMES.join(' | ')}`) if (!a.date || !/^\d{4}-\d{2}-\d{2}$/.test(a.date)) throw new Error('Thiếu args.date (YYYY-MM-DD) — header artifact cần ngày thật') const project = a.project const stage = a.stage const root = `${a.outputRoot || TRACK.outputRoot}/${project}` const skillsDir = '.claude/skills' const def = TRACK.stages[stage] || null if (def && a.activity && !def.activities.includes(a.activity)) { throw new Error(`args.activity "${a.activity}" không thuộc stage ${stage}. Hợp lệ: ${def.activities.join(' | ')}`) } if (stage === 'init' && !a.profile && TRACK.profileFields.length) { throw new Error(`Stage init cần args.profile {${TRACK.profileFields.join(', ')}} đã được người dùng xác nhận`) } if (stage === 'sign' && !(Array.isArray(a.approvals) && a.approvals.length)) { throw new Error('Stage sign cần args.approvals[] {artifact, decision, approver, role, note?}') } // ---------------- Schema ---------------- const STR_ARR = { type: 'array', items: { type: 'string' } } const obj = (props, required) => ({ type: 'object', properties: props, required }) const arr = (item) => ({ type: 'array', items: item }) const S = { type: 'string' } const B = { type: 'boolean' } const N = { type: 'number' } const STAGE_SCHEMA = obj({ filesWritten: STR_ARR, artifacts: arr(obj({ code: S, path: S, version: S, status: S, confidence: S }, ['code', 'path', 'status'])), blocked: B, gateWarning: S, gateSelfCheck: arr(obj({ item: S, ok: B, note: S }, ['item', 'ok'])), openQuestions: arr(obj({ id: S, question: S, askWho: S, blocks: S, ifReversed: S }, ['id', 'question'])), humanInputNeeded: arr(obj({ topic: S, question: S, suggestedDefault: S }, ['topic', 'question'])), assumptions: STR_ARR, decisions: arr(obj({ id: S, text: S }, ['text'])), adrs: arr(obj({ id: S, title: S, status: S, radar: N }, ['id', 'title'])), baSyncIssues: arr(obj({ baArtifact: S, saArtifact: S, issue: S, action: S }, ['issue'])), tbdCount: N, ambiguousCount: N, confidence: { type: 'string', enum: ['high', 'medium', 'low'] }, summary: S, }, ['filesWritten', 'blocked', 'gateSelfCheck', 'openQuestions', 'humanInputNeeded', 'confidence', 'summary']) const AUDIT_SCHEMA = obj({ profile: obj({ product: S, lifecycle: S, rigor: S, confirmed: B }, []), gates: arr(obj({ gate: S, status: { type: 'string', enum: ['✅', '🟠', '☐'] }, artifacts: STR_ARR, missing: STR_ARR, signersRequired: S, lowConfidence: STR_ARR }, ['gate', 'status', 'missing'])), currentPosition: S, blockersHard: STR_ARR, blockersSoft: STR_ARR, coverage: arr(obj({ check: S, value: S, pass: B, details: STR_ARR }, ['check', 'pass'])), baSync: arr(obj({ pair: S, issue: S, action: S }, ['pair', 'issue'])), overdueOQ: arr(obj({ id: S, askWho: S, sinceDate: S, blocks: S, ifReversed: S }, ['id'])), warnings: STR_ARR, nextActions: arr(obj({ action: S, skill: S }, ['action'])), summary: S, }, ['gates', 'currentPosition', 'nextActions', 'summary']) const SIGN_SCHEMA = obj({ filesEdited: STR_ARR, applied: arr(obj({ artifact: S, decision: S, newStatus: S, newVersion: S }, ['artifact', 'decision'])), refused: arr(obj({ artifact: S, reason: S }, ['artifact', 'reason'])), indexUpdated: B, summary: S, }, ['filesEdited', 'applied', 'refused', 'summary']) // ---------------- Khối prompt dùng chung ---------------- const lines = [] lines.push(`Project: ${project}. Thư mục output: ${root}/. Ngày hôm nay (dùng cho header Date/Approved by): ${a.date}.`) lines.push(`Skill điều phối: ${skillsDir}/${TRACK.lifecycleSkill}/ (references bắt buộc) · skill truy vết: ${skillsDir}/${TRACK.traceSkill}/.`) if (a.profile) lines.push(`Profile đã được người dùng xác nhận: ${Object.keys(a.profile).map((k) => `${k}=${a.profile[k]}`).join(' · ')}.`) else if (TRACK.profileFields.length) lines.push(`Profile: đọc ${root}/${TRACK.indexDir}/PROFILE_${project}.md; chưa có ⇒ dùng mặc định standard và NÓI RÕ điều đó.`) if (Array.isArray(a.inputs) && a.inputs.length) lines.push(`Input bổ sung do người dùng cung cấp (ưu tiên cao nhất):\n${a.inputs.map((i) => `- ${i}`).join('\n')}`) if (a.answers) lines.push(`## Câu trả lời của người dùng cho OQ / humanInputNeeded vòng trước\n${a.answers}`) if (a.notes) lines.push(`## Ghi chú từ người duyệt (bắt buộc xử lý trước, version +0.1 và Change Log)\n${a.notes}`) if (a.override) lines.push(`## Ngoại lệ gate\nNgười dùng đã được cảnh báo gate trước chưa ✅ Baselined và KHẲNG ĐỊNH LẠI muốn tiếp tục. Làm tiếp, ghi ngoại lệ vào Open Questions + DEC-nn của artifact.`) const common = lines.join('\n\n') const gateOf = (name) => (TRACK.stages[name] ? TRACK.stages[name].gate : null) const signersOf = (g) => (g && TRACK.gates[g]) || null // ---------------- Kết quả trả về cho người duyệt ---------------- function wrap(kind, res, extra) { const r = res || {} const out = { track: TRACK.label, project, stage, kind, ok: !!res, date: a.date, activity: a.activity || null, scope: a.scope || null, gate: def ? def.gate : (a.gate || null), signersRequired: signersOf(def ? def.gate : a.gate), result: r, } return extra ? Object.assign(out, extra) : out } // ---------------- Stage: init ---------------- if (stage === 'init') { phase('Init') const r = await agent( `${common}\n\nNhiệm vụ "init": làm đúng ${skillsDir}/${TRACK.lifecycleSkill}/SKILL.md (khởi tạo project mới). ` + `Tạo cấu trúc thư mục trong ${root}/, file PROFILE (nếu bộ này có profile) và INDEX theo mẫu artifact-map. ` + `Không tạo file rỗng cho giai đoạn sau. Nếu ${root}/ đã có nội dung ⇒ không ghi đè, trả blocked=true và nêu rõ.`, { agentType: TRACK.runner, label: `${TRACK.label}:init`, schema: STAGE_SCHEMA }, ) return wrap('init', r) } // ---------------- Stage: audit (chỉ đọc) ---------------- if (stage === 'audit') { phase('Audit') const r = await agent( `${common}\n\nNhiệm vụ "audit" (CHỈ ĐỌC): chấm toàn bộ gate theo checklist của ${skillsDir}/${TRACK.lifecycleSkill}/references/workflow.md ` + `(điều chỉnh theo RIGOR nếu có), chạy các phép kiểm coverage/nhất quán của ${skillsDir}/${TRACK.traceSkill}/, ` + `liệt kê blocker cứng/mềm, OQ quá hạn (so với ngày ${a.date}), cảnh báo bắt buộc và tối đa 3 việc tiếp theo. ` + `✅ chỉ khi có chữ ký thật trong header. Không ghi file.${a.scope ? `\nPhạm vi cần soi kỹ: ${a.scope}` : ''}`, { agentType: TRACK.auditor, label: `${TRACK.label}:audit`, schema: AUDIT_SCHEMA }, ) return wrap('audit', r) } // ---------------- Stage: sign (ghi quyết định của con người) ---------------- if (stage === 'sign') { phase('Sign') const list = a.approvals.map((p, i) => `${i + 1}. ${p.artifact} — quyết định: ${p.decision}; người duyệt: ${p.approver}${p.role ? ` (${p.role})` : ''}${p.note ? `; ghi chú: ${p.note}` : ''}`).join('\n') const decs = Array.isArray(a.decisions) && a.decisions.length ? `\nDEC cần ghi vào sổ quyết định:\n${a.decisions.map((d) => `- ${d}`).join('\n')}` : '' const gateLine = a.gate ? `\nGate được ký: ${a.gate} — người ký yêu cầu theo quy trình: ${signersOf(a.gate) || 'xem workflow.md'}. Nếu danh sách người duyệt bên dưới không đủ vai trò ⇒ vẫn ghi Approved by nhưng KHÔNG baseline, và nêu trong refused.` : '' const r = await agent( `${common}\n\nNhiệm vụ "sign": ghi quyết định duyệt của CON NGƯỜI vào header/Change Log/INDEX theo đúng quy tắc trong system prompt của bạn. ` + `Không đổi nội dung chuyên môn. Từ chối baseline khi artifact chưa đủ điều kiện (còn TBD, thiếu header, checklist gate chưa đủ) và nêu lý do.${gateLine}\n\nDanh sách quyết định:\n${list}${decs}`, { agentType: TRACK.runner, label: `${TRACK.label}:sign`, schema: SIGN_SCHEMA }, ) return wrap('sign', r, { gate: a.gate || null, signersRequired: signersOf(a.gate) }) } // ---------------- Stage: sync (INDEX) ---------------- if (stage === 'sync') { phase('Sync') const r = await agent( `${common}\n\nNhiệm vụ "sync": cập nhật ${root}/${TRACK.indexDir}/INDEX_${project}.md (và sổ ADL/DTM/OQ/DECISION nếu bộ này có) ` + `từ header THẬT của các artifact theo ${skillsDir}/${TRACK.lifecycleSkill}/references/artifact-map.md. Không sửa artifact.`, { agentType: TRACK.runner, label: `${TRACK.label}:sync`, schema: STAGE_SCHEMA }, ) return wrap('sync', r) } // ---------------- Stage giai đoạn (ba-1…/sa-1…) ---------------- phase(def.phase) const acts = a.activity ? [a.activity] : def.activities const prereq = [ def.prevGate ? `Gate trước cần ✅ Baselined: ${def.prevGate}.` : 'Đây là giai đoạn đầu, không có gate trước trong bộ này.', def.externalPrereq ? `Điều kiện ngoài bộ: ${def.externalPrereq}.` : '', def.orderedPrefix ? `Thứ tự bắt buộc trong giai đoạn: ${def.orderedPrefix.join(' → ')} phải có trước các hoạt động khác.` : '', ].filter(Boolean).join(' ') if (def.scopeHint && !a.scope) log(`Cảnh báo: stage ${stage} thường cần args.scope (${def.scopeHint}) — agent sẽ tự chọn và báo trong summary.`) const r = await agent( `${common}\n\nNhiệm vụ: thực thi skill ${skillsDir}/${def.skill}/SKILL.md ở chế độ go, ` + `chỉ các hoạt động: ${acts.join(', ')}${a.scope ? ` · phạm vi: ${a.scope}` : ''}. Output vào ${root}/${def.dir}/. ` + `${prereq} Preflight gate trước khi ghi: chưa qua và không có "Ngoại lệ gate" ⇒ blocked=true, không ghi file. ` + `Gate của giai đoạn này: ${def.gate} (người ký: ${signersOf(def.gate)}) — bạn chỉ TỰ CHẤM checklist, không được ghi ✅ vào header.`, { agentType: TRACK.runner, label: `${TRACK.label}:${stage}${a.activity ? ':' + a.activity : ''}`, schema: STAGE_SCHEMA }, ) if (r && r.blocked) log(`${TRACK.label}/${stage}: bị chặn bởi gate — ${r.gateWarning || 'xem result.gateWarning'}`) return wrap('stage', r, { activities: acts, nextGate: def.gate, signersRequired: signersOf(def.gate) })