Từ nửa tiếng lục Jira mỗi sáng, xuống còn một câu gõ :root{ --bg:#0B1220; --surface:#141C2E; --surface-2:#1B2438; --border:rgba(255,255,255,.10); --border-strong:rgba(255,255,255,.18); --text:#F4F6F8; --text-secondary:#93A1B2; --accent:#FFB020; --accent-rgb:255,176,32; --accent-soft:rgba(255,176,32,.15); --accent-2:#4CC9F0; --accent-2-rgb:76,201,240; --accent-2-soft:rgba(76,201,240,.15); --success:#4ADE80; --success-soft:rgba(74,222,128,.15); --danger:#FF6B6B; --danger-soft:rgba(255,107,107,.15); --font-heading:"Segoe UI","Helvetica Neue",Arial,system-ui,-apple-system,sans-serif; --font-body:"Segoe UI","Helvetica Neue",Arial,system-ui,-apple-system,sans-serif; --font-mono:"Consolas","SFMono-Regular","Courier New",monospace; } *{box-sizing:border-box;} html{scroll-behavior:smooth;} body{ margin:0; background-color:var(--bg); background-image:radial-gradient(rgba(255,255,255,.05) 1px, transparent 1px); background-size:28px 28px; color:var(--text); font-family:var(--font-body); font-variant-numeric:tabular-nums; position:relative; min-height:100vh; } body::before,body::after{ content:"";position:fixed;border-radius:50%;filter:blur(120px);z-index:0;pointer-events:none; } body::before{width:620px;height:620px;background:rgba(var(--accent-rgb),.16);top:-240px;left:-220px;} body::after{width:640px;height:640px;background:rgba(var(--accent-2-rgb),.13);bottom:-260px;right:-220px;position:fixed;} .progress-track{position:fixed;top:0;left:0;width:100%;height:4px;background:var(--border);z-index:60;} .progress-fill{height:100%;width:0%;background:var(--accent);box-shadow:0 0 12px rgba(var(--accent-rgb),.7);} .brand{position:fixed;top:16px;left:22px;font-family:var(--font-mono);font-weight:600;font-size:.78rem;color:var(--text-secondary);letter-spacing:.02em;z-index:40;} .brand span{color:var(--accent);} .page{position:relative;z-index:1;max-width:760px;margin:0 auto;padding:110px 24px 90px;} .subtitle{color:var(--text-secondary);font-size:clamp(.98rem,1.5vw,1.1rem);line-height:1.6; margin:0 0 46px;max-width:52ch;} .subtitle code{background:var(--surface-2);color:var(--accent);padding:2px 7px;border-radius:5px;font-family:var(--font-mono);font-size:.9em;border:1px solid var(--border);} /* Table of contents */ .toc{ position:fixed;top:150px;left:calc(50% - 760px/2 - 30px);width:212px; transform:translateX(-100%);z-index:30; } .toc-label{font-family:var(--font-mono);font-weight:600;font-size:.7rem;letter-spacing:.12em; text-transform:uppercase;color:var(--text-secondary);margin:0 0 12px;} .toc ol{list-style:none;margin:0;padding:0;border-left:1px solid var(--border);} .toc li{margin:0;} .toc a{ display:block;padding:7px 0 7px 16px;margin-left:-1px;border-left:1px solid transparent; font-size:.86rem;line-height:1.4;color:var(--text-secondary);text-decoration:none; transition:color .15s ease,border-color .15s ease; } .toc a:hover{color:var(--text);} .toc a.active{color:var(--accent);border-left-color:var(--accent);font-weight:600;} @media (max-width:1360px){.toc{display:none;}} .eyebrow{ display:inline-flex;align-items:center;gap:8px; font-family:var(--font-mono);font-weight:600;font-size:.74rem;letter-spacing:.08em; text-transform:uppercase;padding:6px 14px;border-radius:999px; background:rgba(255,255,255,.08);color:#fff;margin-bottom:22px; } h1{font-family:var(--font-heading);font-weight:800;letter-spacing:-.015em;line-height:1.16; font-size:clamp(1.9rem,4.6vw,3rem);margin:0 0 16px;color:#fff;max-width:26ch;} h2.chapter-title{font-family:var(--font-heading);font-weight:800;font-size:clamp(1.35rem,2.6vw,1.9rem); line-height:1.28;letter-spacing:-.01em;color:var(--text);margin:0;} .chapter{margin:0 0 64px;padding-top:8px;} .chapter:last-of-type{margin-bottom:0;} .chapter-head{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin-bottom:22px;} .chapter-label{ display:inline-block;font-family:var(--font-mono);font-weight:700;font-size:.76rem;letter-spacing:.1em; text-transform:uppercase;color:var(--accent); padding:6px 16px;border:1px solid rgba(var(--accent-rgb),.35);border-radius:999px; background:var(--accent-soft); } .chapter-body p{ font-size:1.04rem;line-height:1.78;color:var(--text);margin:0 0 20px; } .chapter-body p:last-child{margin-bottom:0;} .chapter-body strong{color:var(--text);font-weight:700;} .chapter-body em{color:var(--text-secondary);font-style:italic;} .chapter-body code{background:var(--surface-2);color:var(--accent);padding:2px 7px;border-radius:5px;font-family:var(--font-mono);font-size:.88em;border:1px solid var(--border);} .lede{color:var(--text-secondary) !important;font-size:1.08rem !important;} /* Screenshot card */ .shot{background:var(--surface);border:1px solid var(--border);border-radius:14px; padding:10px 10px 14px;box-shadow:0 20px 50px rgba(0,0,0,.45);margin:26px 0;overflow:hidden;position:relative;z-index:0;} .shot .bar{display:flex;gap:6px;margin:2px 0 10px 6px;} .shot .bar span{width:9px;height:9px;border-radius:50%;display:inline-block;} .shot .bar span:nth-child(1){background:#FF6B6B;} .shot .bar span:nth-child(2){background:#FFB020;} .shot .bar span:nth-child(3){background:#4ADE80;} .shot .frame{border-radius:8px;overflow:hidden;background:#fff;position:relative;} .shot .frame img{display:block;width:100%;height:auto;cursor:zoom-in;position:relative;z-index:0;} .shot figcaption{margin:12px 4px 2px;font-size:.86rem;font-style:italic;color:var(--text-secondary);line-height:1.5;} .shot--uniform .frame{height:300px;display:flex;align-items:center;justify-content:center;overflow:hidden;} .shot--uniform .frame img{width:auto;height:auto;max-width:100%;max-height:100%;object-fit:contain;cursor:zoom-in;min-width:0;min-height:0;} @media (max-width:600px){.shot--uniform .frame{height:220px;}} /* Chapter 4 — 4 principle cards */ .principle-grid{display:grid;grid-template-columns:1fr;gap:16px;margin:28px 0;} .principle-card{background:var(--surface);border:1px solid var(--border);border-left:3px solid var(--accent-2); border-radius:12px;padding:20px 22px;box-shadow:0 10px 30px rgba(0,0,0,.35);} .principle-card .p-num{font-family:var(--font-mono);font-weight:800;font-size:.72rem;letter-spacing:.1em; margin-bottom:10px;} .principle-card h4{font-family:var(--font-heading);font-weight:700;font-size:1.06rem;color:var(--text);margin:0 0 10px;} .principle-card p{font-size:.94rem;line-height:1.62;color:var(--text-secondary);margin:0 0 10px;} .principle-card p:last-child{margin-bottom:0;} .principle-card em{color:var(--text);font-style:italic;} .principle-card p strong{color:var(--text);} .principle-card--c1{border-left-color:var(--accent-2);} .principle-card--c1 .p-num{color:var(--accent-2);} .principle-card--c2{border-left-color:var(--accent);} .principle-card--c2 .p-num{color:var(--accent);} .principle-card--c3{border-left-color:var(--danger);} .principle-card--c3 .p-num{color:var(--danger);} .principle-card--c4{border-left-color:var(--success);} .principle-card--c4 .p-num{color:var(--success);} .principle-card .example{margin-top:14px;padding:12px 16px;background:var(--surface-2); border-radius:8px;border-left:2px solid var(--border-strong);} .principle-card .example-label{font-family:var(--font-mono);font-weight:700;font-size:.68rem; letter-spacing:.1em;text-transform:uppercase;color:var(--text-secondary);margin-bottom:6px;} .principle-card .example p{font-size:.9rem;font-style:italic;color:var(--text-secondary);margin:0;} /* Paired screenshots (2 images sharing one frame) */ .pair-card{background:var(--surface);border:1px solid var(--border);border-radius:14px; padding:10px 10px 14px;box-shadow:0 20px 50px rgba(0,0,0,.45);margin:26px 0;} .pair-card .bar{display:flex;gap:6px;margin:2px 0 10px 6px;} .pair-card .bar span{width:9px;height:9px;border-radius:50%;display:inline-block;} .pair-card .bar span:nth-child(1){background:#FF6B6B;} .pair-card .bar span:nth-child(2){background:#FFB020;} .pair-card .bar span:nth-child(3){background:#4ADE80;} .pair-row{display:grid;grid-template-columns:1fr 1fr;gap:12px;} .shot--pair{background:none;border:none;box-shadow:none;padding:0;margin:0;} .shot--pair .frame{border-radius:8px;overflow:hidden;background:#fff;height:200px; display:flex;align-items:center;justify-content:center;} .shot--pair .frame img{width:auto;height:auto;max-width:100%;max-height:100%;object-fit:contain;cursor:zoom-in;min-width:0;min-height:0;} .shot--pair figcaption{margin:8px 2px 0;font-size:.76rem;font-style:italic;color:var(--text-secondary);line-height:1.4;} @media (max-width:600px){.pair-row{grid-template-columns:1fr;}.shot--pair .frame{height:auto;}.shot--pair .frame img{height:auto;}} /* Callout */ .callout{border-radius:12px;padding:16px 20px;font-size:.98rem;line-height:1.65; border:1px solid transparent;margin:22px 0;} .callout--warm{background:var(--accent-soft);color:#FFE8C2;border-color:rgba(var(--accent-rgb),.35);} .callout--accent{background:var(--accent-2-soft);color:#D8F3FF;border-color:rgba(76,201,240,.35);} .callout strong{color:inherit;} /* Steps */ ol.steps{list-style:none;margin:20px 0;padding:0;display:flex;flex-direction:column;gap:20px;counter-reset:step;} ol.steps > li{position:relative;padding-left:40px;font-size:1.02rem;line-height:1.72;color:var(--text);counter-increment:step;} ol.steps > li::before{ content:counter(step);position:absolute;left:0;top:0; width:26px;height:26px;border-radius:50%;background:var(--accent-soft);color:var(--accent); display:flex;align-items:center;justify-content:center; font-family:var(--font-mono);font-weight:700;font-size:.8rem; } /* Code / command blocks */ pre.cmd{background:var(--surface-2);border:1px solid var(--border);border-radius:10px; padding:14px 18px;margin:14px 0;overflow-x:auto;} pre.cmd code{font-family:var(--font-mono);font-size:.92rem;color:var(--accent-2);background:none;border:none;padding:0;} .cmd-label{font-family:var(--font-mono);font-size:.72rem;font-weight:600;letter-spacing:.06em; text-transform:uppercase;color:var(--text-secondary);margin:16px 0 6px;} ol.steps > li .cmd-label:first-of-type{margin-top:10px;} pre.yaml{background:var(--surface-2);border:1px solid var(--border);border-radius:10px; padding:16px 20px;margin:16px 0;overflow-x:auto;} pre.yaml code{font-family:var(--font-mono);font-size:.86rem;color:var(--text);line-height:1.6;background:none;border:none;padding:0;white-space:pre;} pre.yaml .k{color:var(--accent-2);} pre.yaml .c{color:var(--text-secondary);font-style:italic;} /* CTA */ .cta{margin-top:16px;position:relative;z-index:10;} .cta-link{ display:inline-flex;align-items:center;justify-content:center;gap:10px; background:var(--accent);color:#1a1200; font-weight:700;font-size:.95rem;letter-spacing:.01em; padding:13px 22px;border-radius:999px;text-decoration:none; box-shadow:0 10px 28px rgba(var(--accent-rgb),.28); cursor:pointer;pointer-events:auto;position:relative;z-index:10; max-width:100%; } .cta-link:hover{filter:brightness(1.06);} /* Lightbox */ .lightbox{position:fixed;inset:0;background:rgba(4,7,14,.92);display:none;align-items:center;justify-content:center;padding:6vh 6vw;z-index:200;cursor:zoom-out;pointer-events:none;} .lightbox.active{display:flex;pointer-events:auto;} .lightbox img{max-width:100%;max-height:100%;border-radius:10px;box-shadow:0 30px 80px rgba(0,0,0,.6);cursor:default;} @media (max-width:600px){ .page{padding:96px 16px 70px;} .chapter{margin-bottom:52px;} } /* ===== Bổ sung cho văn phong báo điện tử ===== */ .meta-row{display:flex;flex-wrap:wrap;align-items:center;gap:9px;margin:0 0 22px; font-family:var(--font-mono);font-size:.74rem;letter-spacing:.03em;color:var(--text-secondary);} .meta-row .sep{opacity:.4;} .sapo{font-size:clamp(.94rem,1.3vw,1.01rem);line-height:1.68;color:var(--text); margin:0 0 30px;max-width:none;padding-left:18px;border-left:3px solid var(--accent);} .sapo code{font-size:.86em;} .sapo strong{color:#fff;font-weight:700;} .sapo code{background:var(--surface-2);color:var(--accent);padding:2px 7px;border-radius:5px; font-family:var(--font-mono);font-size:.88em;border:1px solid var(--border);} .quickread{background:var(--surface);border:1px solid var(--border);border-radius:14px; padding:20px 24px 22px;margin:0 0 54px;box-shadow:0 14px 36px rgba(0,0,0,.35);} .quickread .qr-label{font-family:var(--font-mono);font-weight:700;font-size:.7rem;letter-spacing:.12em; text-transform:uppercase;color:var(--accent);margin-bottom:14px;} .quickread ul{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:11px;} .quickread li{position:relative;padding-left:18px;font-size:.96rem;line-height:1.62;color:var(--text-secondary);} .quickread li::before{content:"";position:absolute;left:0;top:.66em;width:6px;height:6px; border-radius:50%;background:var(--accent-2);} .quickread li strong{color:var(--text);} .quickread code{background:var(--surface-2);color:var(--accent);padding:1px 6px;border-radius:5px; font-family:var(--font-mono);font-size:.86em;border:1px solid var(--border);} h3.sub{font-family:var(--font-heading);font-weight:700;font-size:clamp(1.04rem,1.9vw,1.16rem); line-height:1.42;color:var(--text);margin:36px 0 14px;letter-spacing:-.005em;} .chapter-body > h3.sub:first-child{margin-top:0;} h3.sub::before{content:"— ";color:var(--accent);} .pullquote{margin:32px 0;padding:2px 0 2px 22px;border-left:3px solid var(--accent); font-family:var(--font-heading);font-weight:700;letter-spacing:-.01em; font-size:clamp(1.14rem,2.3vw,1.38rem);line-height:1.5;color:#fff;} PM Workflow · Kaopiz Mục lục 1. Trước giờ standup 2. Khi team đông 3. Hai góc nhìn 4. Nguyên tắc bên trong 5. Chuẩn bị trước khi dùng 6. Đưa báo cáo lên Teams // Chia sẻ nội bộ · Kaopiz Từ nửa tiếng lục Jira mỗi sáng, xuống còn một câu gõ PM Workflow· Dành cho PM/TL· Đọc khoảng 8 phút Mỗi sáng, trước standup, không ít PM/TL mất 30–60 phút lục Jira và Bitbucket chỉ để biết hôm qua team đã làm gì — và vẫn sót. Skill execute-daily-report gom đúng phần việc lặp đi lặp lại đó: một câu gõ trong chat, vài phút sau có báo cáo daily dựng từ dữ liệu thật, kèm cả những ticket đang âm thầm quá hạn. Bài này kể lại skill làm được gì, và làm sao để dùng được ngay hôm nay. Đọc nhanh Vấn đề: team từ 10 người trở lên, tracking tay mỗi sáng ngốn 30–60 phút mà vẫn bỏ lọt ticket. Giải pháp: một câu gõ, skill đọc Jira + Bitbucket rồi trả về báo cáo — chỉ còn vài phút. Hai kiểu báo cáo: pm-summary cho standup buổi sáng, dev-detail cho 1-1 và dọn Jira. Cần gì: Cursor, MCP Jira/Bitbucket, Python. Cài một lần, khai báo dự án một lần, dùng mãi. Chương 1 8 giờ rưỡi sáng, trước giờ standup 8 giờ rưỡi sáng. Còn mười lăm phút nữa là đến giờ họp. Bạn mở Jira, lọc theo từng người, xem hôm qua ai đổi status gì. Xong lại mở Bitbucket: PR nào đã merge, PR nào mở cả tuần mà chưa ai review. Rồi quay lại Jira lần nữa. Lần này để soi ticket nào quá hạn, ticket nào due tuần này mà chưa thấy động tĩnh. Dù soi kỹ đến đâu, vẫn dễ sót. Một ticket ai đó âm thầm đổi status rồi quên log giờ. Một PR mở đã hai ngày, không ai đụng tới. Một ticket "Confirmming" chờ khách hàng trả lời, nằm im cả tháng mà chẳng ai để ý. Đến lúc có người hỏi tới thì cũng chẳng ai nhớ nổi nó còn sống hay đã chết tự bao giờ. Và đây mới chỉ là một dự án, một buổi sáng. Chương 2 Khi team đông, tracking tay không còn là chuyện nhỏ Mười phút, rồi thành một tiếng Team dưới 5 người thì việc trên mất chừng mười phút. Nhưng với team từ 10 người trở lên — vài DEV, vài QA, một BrSE, đôi khi thêm PM khác cùng tham gia — mười phút ấy dễ dàng thành nửa tiếng. Có hôm gần một tiếng. Quản cùng lúc vài dự án như vậy, con số đó nhân lên nhanh hơn bạn tưởng. Cái giá không chỉ là thời gian Vấn đề không nằm ở năng lực hay sự tận tâm của PM/TL. Nó nằm ở chỗ: ngồi tổng hợp tay từng ấy nguồn dữ liệu, cho từng ấy con người, mỗi ngày, thì kiểu gì cũng có lúc hụt hơi. Và những thứ hay bị bỏ sót nhất — logwork thiếu, ticket trễ hạn, ticket im lặng quá lâu — lại thường chính là những tín hiệu hay bị soi khi chấm điểm PCV của dự án. Tức là tracking không kịp đôi khi không chỉ mất thời gian. Nó còn mất điểm. May thay, giờ đã có một skill xử lý đúng phần việc lặp đi lặp lại này. Chương 3 Một skill, hai góc nhìn PM cần nhìn dự án theo hai cách khác nhau, tùy thời điểm. Buổi sáng trước standup, cần đứng ngoài nhìn cả team: hôm qua ai làm gì, đang vướng ở đâu. Lúc ngồi 1-1, hay khi vừa tiếp quản một dự án còn mù mờ, lại cần soi vào từng người: ai đang giữ ticket nào, ai đang thiếu bước nào. Hai góc nhìn khác nhau. Nên skill execute-daily-report cho ra hai kiểu báo cáo khác nhau — cùng một skill, chỉ đổi cách gõ. Báo cáo tổng hợp: bạn của buổi standup Kiểu đầu tiên là báo cáo tổng hợp (pm-summary). Nó kể lại hôm qua team đã làm gì — theo đúng người thực sự thao tác, không phải người đang được assign — đang vướng ở đâu, và có rủi ro nào cần cả team ngồi lại bàn không. Trước giờ họp, đọc theo thứ tự: mục 1 để gọi đúng người, mục 2 để hỏi block, mục 3 để lại sau standup. Mọi Jira key và PR trên báo cáo đều bấm được. Không phải search lại. Báo cáo tổng hợp buổi sáng: việc từng người đã làm hôm qua, key/PR dạng link. Block/vướng mắc và rủi ro — đọc mục 2 trong họp, mục 3 để sau. Báo cáo chi tiết team: bạn của buổi 1-1 Kiểu thứ hai là báo cáo chi tiết team (dev-detail). Đây là bạn của những buổi 1-1, lúc dọn dẹp Jira, hay khi PM mới tiếp quản dự án. Tên gọi kỹ thuật là "dev-detail", nhưng đừng để cái tên đánh lừa. Nó tổng hợp mọi vai trò trong dự án, không chỉ lập trình viên — chỉ trừ đúng role PM. Mở file này thì đọc dòng Member nên nhắc trước, ngay trong mục Nhìn nhanh. Đó là danh sách ai cần được PM hỏi logwork hay dọn ticket — chưa phải lúc soi từng bảng. Các bảng checkpoint — bug đang mở, quá hạn, due 2 tuần, Task Done gần đây — là bức tranh cả dự án, không phải "theo từng người". Muốn nhìn từng member thì kéo xuống Tổng kết nhanh. Bảng này cho biết ai log giờ, ai có PR, ai đang giữ bug — đủ để hỏi đúng người, đúng việc. Nhìn nhanh: số bug/quá hạn, và dòng Member nên nhắc — đọc dòng này trước. Tổng kết nhanh theo member: task, worklog, PR, bug open — dùng lúc 1-1. Vậy khi nào dùng kiểu nào? Summary mỗi sáng — mặc định lấy hoạt động hôm qua — là đủ cho standup. Detail thì không phải routine mỗi ngày. Mở nó khi 1-1, khi tiếp quản dự án, khi dọn Jira, hoặc trước lúc các tín hiệu logwork/duedate/ticket im lặng bị soi. Cần cả hai thì gõ một lần, skill trả về hai file. Chương 4 Bên trong: nguyên tắc nào làm nên sự khác biệt Nếu chỉ là gom số liệu từ Jira và Bitbucket rồi in ra bảng, một đoạn script cũng làm được. Cái khiến execute-daily-report đáng nói đến không nằm ở chỗ nó có dữ liệu, mà ở cách nó đọc hiểu dữ liệu đó. Bốn nguyên tắc dưới đây là phần khác biệt. 01 Ghi việc cho đúng người đã làm Trong một ngày, một ticket có thể đi qua tay nhiều người: người này làm xong phần của mình rồi giao lại cho người sau xử lý tiếp. Báo cáo phải nói đúng ai đã làm việc gì. Nếu chỉ nhìn ô "assignee" — tức người đang được giao ticket ở thời điểm đọc — báo cáo sẽ kể tên người vừa nhận, chứ không phải người vừa làm xong. Skill không nhìn ô đó. Nó lần lại lịch sử ticket để tìm ai là người đổi trạng thái, ai mở/merge/review PR, rồi viết việc vào tên người đó. Người nhận ticket sau chỉ hiện lên bằng một dòng ngắn: "nhận assign". Ví dụ Sáng qua, một DEV code xong, tự chuyển ticket sang "Resolved" rồi giao cho QA test. Đến chiều QA mới nhận ticket về mình. Nếu chỉ nhìn "assignee", báo cáo sẽ viết việc đó vào tên QA — người còn chưa kịp mở ticket lên xem. Skill đọc lại lịch sử thay đổi, thấy DEV mới là người bấm nút chuyển trạng thái, nên việc được ghi cho DEV; QA chỉ nhận một dòng "nhận assign để test". 02 Lọc nhiễu Họp, trao đổi khách hàng, thao tác vận hành không được tính vào mục "task đã làm" — kể ra chỉ làm báo cáo dài thêm, không giúp gì cho standup. Giờ log của những việc đó vẫn được cộng đủ, không mất đi đâu cả. 03 Ticket đang "im lặng" Mỗi dòng checkpoint kèm số ngày kể từ lần cập nhật gần nhất, nên không cần mở từng ticket để xem ô updated. Ticket tồn cả tháng, cả quý hiện lên trong cùng một bảng. Nhìn là biết: còn sống, nên đóng, hay cần nhắc member cập nhật. 04 Ticket đi tắt quy trình Có ticket đã Resolved/Deployed mà chưa log phút nào. Có ticket đã log giờ nhưng chưa từng có ước lượng effort. Mục Quy trình gom cả hai nhóm này theo từng người — PM đọc đúng tên, đúng ticket, rồi nhắc; không cần lục cả board Jira. Ticket sắp đến hạn kèm số ngày không cập nhật, và Quy trình: chưa logwork / chưa ước lượng theo người. Cộng bốn điều trên lại chính là lý do một skill khác hẳn một cái script chấm công. Nó trả lại đúng phần thời gian PM hay mất mỗi sáng, giúp thấy block và rủi ro sớm hơn một nhịp, và tự tay chỉ ra ai đang thiếu bước nào — thay vì để PM tự mò từng ticket. Còn nếu Jira hay Bitbucket không trả về gì, skill nói thẳng "không có dữ liệu", chứ không bịa cho xuôi. Vài điều để không kỳ vọng quá tay: skill không thay standup, chỉ giúp bạn bước vào họp đủ thông tin hơn. Nó cũng không tự dọn Jira — vẫn là PM nhắc member. Và mỗi lần chạy là một lát cắt của một ngày, không phải dashboard real-time. Chương 5 Chuẩn bị trước khi dùng Cần gì trên máy Máy cần Cursor (Agent mode), MCP nối Jira và Bitbucket, và Python. Có thêm Ksystem thì roster đầy đủ hơn. Nhưng nếu dự án chưa nối được Ksystem, skill vẫn chạy bình thường — chỉ là roster/phân bổ sẽ suy từ ai có hoạt động trên Jira. Không sai, chỉ chưa đầy đủ bằng danh sách chính thức. Nếu chat báo không gọi được Jira/Bitbucket thì bật MCP hoặc nhờ IT. Thiếu MCP thì skill không chạy. Ba việc cần làm Lấy skill từ repo nội bộ (link ở chương cuối), copy vào ~/.cursor/skills/pm/execute-daily-report/ (Windows: %USERPROFILE%\.cursor\skills\pm\execute-daily-report\). Cursor chạy bản global này, không phải folder trong git. Khai báo dự án vào ~/.config/pm-workflow/projects.yaml — tối thiểu jira_key và bitbucket_project_key. Muốn bắn Teams thì thêm teams_webhook_url (cách lấy ở chương sau). Điền key của dự án bạn; khối dưới chỉ là minh họa: projects: RFH: name: Renew jira_key: RFH bitbucket_project_key: YOUR_BB_KEY # teams_webhook_url: https://... # dán sau khi copy webhook Mỗi dự án một webhook, trỏ đúng channel của dự án đó — đừng dùng chung một link cho nhiều team. Gõ trong chat — luôn bắt đầu bằng /execute-daily-report, phần sau đó tả bằng tiếng Việt tự nhiên (dự án nào, mode nào, có post Teams hay không): /execute-daily-report báo cáo daily RFH /execute-daily-report báo cáo chi tiết team FDB Thêm --teams vào cuối câu để chạy xong gửi thẳng lên Teams luôn /execute-daily-report báo cáo daily RFH --teams File đã có sẵn từ trước, chỉ muốn đẩy lên Teams /execute-daily-report post file .agent/daily-report/WO-2026-09-08-detail.md lên teams Một lưu ý về ngày tháng Báo cáo mặc định lấy hôm qua. File nằm tại .agent/daily-report/{KEY}-{YYYY-MM-DD}-{summary|detail}.md, trong đó {YYYY-MM-DD} là ngày hoạt động — không phải ngày bạn đang ngồi gõ. Title của báo cáo summary thì lại là sáng hôm sau. Ảnh FDB ở chương 3 là ví dụ: tiêu đề 09/09 nhưng số liệu ngày 08/09. Đó là đúng, không phải skill sai ngày. Chương 6 Để báo cáo tự tìm đến team File .md đọc được là một chuyện. Nhưng tiện nhất vẫn là để báo cáo tự nhảy vào đúng channel Teams mỗi sáng, không cần ai copy-paste. Chỉ cần lấy webhook một lần cho mỗi dự án: Tạo channel Teams muốn nhận báo cáo mỗi sáng. Trong channel đó, mở menu ... (More options) → Workflows → tìm "webhook" → chọn template "Send webhook alerts to a channel". Tìm template gửi cảnh báo webhook trong Teams. Chọn đúng Team và Channel vừa tạo → Save. Chọn Team và Channel sẽ nhận báo cáo. Workflow chuyển Active → bấm Copy webhook link. Nút Copy webhook link sau khi workflow Active. Dán URL vào teams_webhook_url của đúng project trong projects.yaml (field đã khai ở chương 5). Nếu có trục trặc Team không bật được Workflows? Nhờ admin, hoặc mở file .md rồi share tay — vẫn dùng được bình thường. Thi thoảng công cụ báo lỗi dù tin vẫn vào channel. Teams trả "đã nhận" khác với "thành công"; mở channel kiểm tra là chắc nhất. Ảnh dưới là channel thử, không phải channel production bắt buộc. Báo cáo được bot Workflows đăng vào channel Teams (môi trường thử). Skill gốc do TrongPQ phát triển; bản đang dùng được tinh chỉnh thêm cho sát nhịp dự án thực tế. Lấy về tại repo nội bộ pm-workflow: Mở execute-daily-report trên Kari var fill = document.getElementById('progressFill'); function updateProgress(){ var h = document.documentElement; var scrollTop = h.scrollTop || document.body.scrollTop; var scrollHeight = (h.scrollHeight || document.body.scrollHeight) - h.clientHeight; var pct = scrollHeight > 0 ? (scrollTop / scrollHeight) * 100 : 0; fill.style.width = pct + '%'; } document.addEventListener('scroll', updateProgress, {passive:true}); updateProgress(); var tocLinks = document.querySelectorAll('.toc a'); var chapters = document.querySelectorAll('.chapter[id]'); if ('IntersectionObserver' in window && tocLinks.length){ var observer = new IntersectionObserver(function(entries){ entries.forEach(function(entry){ if (entry.isIntersecting){ tocLinks.forEach(function(link){ link.classList.toggle('active', link.getAttribute('data-ch') === entry.target.id); }); } }); }, {rootMargin:'-45% 0px -50% 0px', threshold:0}); chapters.forEach(function(ch){ observer.observe(ch); }); } var lightbox = document.getElementById('lightbox'); var lightboxImg = document.getElementById('lightboxImg'); document.querySelectorAll('.shot img').forEach(function(img){ img.addEventListener('click', function(){ lightboxImg.src = img.src; lightboxImg.alt = img.alt; lightbox.classList.add('active'); }); }); lightbox.addEventListener('click', function(){ lightbox.classList.remove('active'); lightboxImg.src = ''; });