Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 93 additions & 6 deletions lib/ac-line.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,19 @@
// SUSPECT must be strictly LOOSER than AC_LINE or it detects nothing new — the bullet
// marker is optional because real contracts declare criteria as bold headings too
// (`**AC-1 — Strip shows …**`, 13 of them in video-compose-progress-ux).
const AC_SUSPECT = /^\s*(?:[-*]\s+)?\*{0,2}\s*AC-\d+\b/;
// NỚI (hồ sơ cong-nguoi-doc-du-nguon): 38 hợp đồng trên 11 kho khai tiêu chí bằng
// TIÊU ĐỀ `### AC-n`; với chúng khuôn cũ cho m = 0, nên nhánh `n===0 && m>0` không
// bao giờ đúng và bộ dò IM ở 34 trong 38 — đúng chỗ nó sinh ra để kêu.
const AC_SUSPECT = /^\s*(?:#{2,6}\s+)?(?:[-*]\s+)?\*{0,2}\s*AC-\d+\b/;
// …but an id followed by a separator opens a CROSS-REFERENCE, not a declaration
// (`**AC-5, AC-9, AC-10 chưa có gì**` in a Notes paragraph). Counting those cries wolf
// on a healthy contract, which the contract's own control case forbids.
const AC_XREF = /^\s*(?:[-*]\s+)?\*{0,2}\s*AC-\d+\s*[,;/]/;
// TIỀN TỐ PHẢI CÂN VỚI AC_SUSPECT. Bản trước nới SUSPECT sang tiêu đề mà bỏ quên
// XREF sinh đôi, nên một tham chiếu chéo viết dạng tiêu đề vừa được đếm là nghi
// ngờ vừa không bóc được → m > n → cờ SHORT nổ oan trên hợp đồng lành, khoá luôn
// dòng một-chạm. Hai khuôn này đọc CÙNG một hình dạng dòng, nên tiền tố của chúng
// là MỘT thứ: sửa một cái mà quên cái kia là dựng lại đúng lớp lỗi ấy.
const AC_XREF = /^\s*(?:#{2,6}\s+)?(?:[-*]\s+)?\*{0,2}\s*AC-\d+\s*[,;/]/;
// Scope: inside the criteria section when there IS one, whole file when there is not.
// Whole-file everywhere over-counts — a Known-limits note like `- **AC-4** — …:` is
// declaration-shaped but is not a declaration (measured: s4-scope-triage line 88 raised
Expand All @@ -31,10 +39,9 @@ const AC_XREF = /^\s*(?:[-*]\s+)?\*{0,2}\s*AC-\d+\s*[,;/]/;
// duyệt riêng trước đây của file này là đúng thứ `findings-section-boundary` vừa
// gỡ khỏi gate-card.js ("xoá bản sao section()"); giữ nó lại là dựng lại y hệt
// điều kiện sinh lỗi mà cả hai feature đang đi đóng.
const { sectionLines } = require('./md-section.cjs');
const { sectionLines, dongKhoi } = require('./md-section.cjs');
function acBlindSpot(contractText, parsedIds) {
const inSec = sectionLines(contractText, 'Criteria');
const scan = inSec.length ? inSec : contractText.split('\n').map((l, i) => ({ no: i + 1, l }));
const scan = criteriaLines(contractText);
const suspect = [];
for (const { no, l } of scan) { if (AC_SUSPECT.test(l) && !AC_XREF.test(l)) suspect.push(no); }
const m = suspect.length, n = parsedIds.length;
Expand Down Expand Up @@ -108,4 +115,84 @@ function parseAC(line) {
return { id, gwt, judgment, crossLayer };
}

module.exports = { AC_LINE, AC_SUSPECT, AC_XREF, parseAC, acBlindSpot, blindSpotText };
// Tên mục tiêu chí trôi qua ba cách viết trên 11 kho đo được. So khớp của
// sectionLines KHÔNG phân biệt hoa thường, nên `Acceptance criteria` trùng
// `Acceptance Criteria` và mục thứ ba chỉ là trang trí — bỏ, kẻo dạy sai người
// sau rằng danh sách này phân biệt hoa thường (lượt chấm 4).
const CRITERIA_HEADINGS = ['Criteria', 'Acceptance Criteria'];
// CHỈ acBlindSpot gọi hàm này, và quét cả tệp ở đây là ĐÚNG: nó chỉ làm số NGHI
// NGỜ to lên, tức làm cờ dễ kêu hơn — sai về phía an toàn. Bộ BÓC tiêu chí gọi
// criteriaLinesScoped; đừng đấu lại hai đường này vào nhau, lượt chấm 4 và 5 đều
// chết vì đúng chỗ đó.
function criteriaLines(contractText) {
const trongMuc = criteriaLinesScoped(contractText);
if (trongMuc.length) return trongMuc;
return String(contractText == null ? '' : contractText).split('\n').map((l, i) => ({ no: i + 1, l }));
}
// HAI phạm vi quét, CỐ Ý khác nhau — đây là chỗ lượt chấm 4 và 5 cùng chết.
//
// criteriaLinesScoped → bộ BÓC tiêu chí. Không có mục thì trả RỖNG.
// criteriaLines → bộ ĐẾM nghi ngờ của acBlindSpot. Không có mục thì quét cả tệp.
//
// Vì sao không dùng chung: quét cả tệp để ĐẾM là vô hại, nó chỉ làm cờ kêu to hơn.
// Quét cả tệp để BÓC thì máy bịa tiêu chí — đo được trên cây trước bản này: một
// hợp đồng không có mục tiêu chí, có `- AC-4:` và `- AC-9:` nằm trong mục «Known
// limits», cho ra thẻ Cổng Phạm vi liệt AC-1, AC-4, AC-9 như tiêu chí phải làm,
// cờ điểm-mù IM (vì hai bên cùng đếm một tập dòng nên n luôn ≥ m), và dòng một-chạm
// mở sẵn chữ «duyệt». Người bấm duyệt một danh sách máy tự nghĩ ra.
//
// Bán kính của nhát cắt, đo trên 1 243 hợp đồng thật của 22 kho: 139 hồ sơ với
// 1 110 tiêu chí đi qua nhánh CÓ MỤC và giữ nguyên; 81 hồ sơ với 380 tiêu chí đi
// qua nhánh quét-cả-tệp và bị trả về hành vi cũ — trong đó 78 hồ sơ vốn ĐÃ có cờ
// đỏ ở cây gốc, nên nhát cắt không sinh tiếng ồn mới, nó chỉ thôi bịa.
// Hợp đồng muốn được đọc thì khai một mục tiêu chí; cờ `blank` nói đúng câu đó.
function criteriaLinesScoped(contractText) {
for (const h of CRITERIA_HEADINGS) {
const ls = sectionLines(contractText, h);
if (ls.length) return ls;
}
return [];
}
// Tiêu chí khai bằng TIÊU ĐỀ: `### AC-n — nhãn`, thân là các dòng tới tiêu đề kế.
const AC_HEAD = /^#{2,6}\s+\*{0,2}\s*(AC-\d+)\b\*{0,2}\s*(.*)$/;
// parseACBlock — lối vào MỚI, đọc theo KHỐI. parseAC(line) giữ nguyên chữ ký nên
// ba bên gọi cũ và lớp vendored ở 9 kho không phải đổi cùng lượt.
// Dạng tiêu đề dựng lại thành một dòng gạch-đầu-dòng rồi cho parseAC chấm: hai
// dạng khai KHÔNG được có hai luật đọc tag, vì đó chính là lớp lỗi hồ sơ này đi
// đóng. Nhãn nối trước thân để `(judgment)` ở nhãn vẫn đọc theo luật nhãn-lỏng.
// TRẢ ĐỦ, KHÔNG GỘP id trùng: bên gọi tự gộp theo luật của mình, và thẻ Cổng
// Phạm vi dựa vào chính chỗ trùng đó để cảnh báo «trùng mã tiêu chí». Gộp ở đây
// là giết cảnh báo ấy một cách lặng lẽ — đo được: policy-graph-hub/registry-params
// khai AC-3, AC-7, AC-11, AC-16 mỗi mã hai lần.
function parseACBlock(contractText) {
const out = [];
let cur = null;
const chot = () => {
if (!cur) return;
const than = cur.than.join(' ').replace(/\s+/g, ' ').trim();
const nhan = cur.nhan.replace(/^[\s—–-]+/, '').trim();
const a = parseAC(`- ${cur.id}${nhan ? ' ' + nhan : ''}: ${than}`);
if (a) out.push(a);
cur = null;
};
for (const { l } of criteriaLinesScoped(contractText)) {
const h = l.match(AC_HEAD);
if (h) { chot(); cur = { id: h[1], nhan: h[2] || '', than: [] }; continue; }
// ĐÓNG KHỐI theo ĐÚNG luật của md-section: h2..h6 đóng, h1 là NỘI DUNG. Bản
// trước tự viết `/^#{1,6}\s/` nên nó đóng ở cả h1, tức hai bộ duyệt bất đồng
// về ranh giới — chính lớp lỗi hồ sơ này đi đóng (lượt chấm 7). Luật ở
// md-section.cjs, không chép lại vào đây.
if (dongKhoi(l)) { chot(); continue; }
// Một dòng GẠCH ĐẦU DÒNG khai tiêu chí cũng ĐÓNG khối tiêu đề đang mở (lượt
// chấm 3 bắt được). Bản trước nuốt nó vào thân của tiêu chí phía trên, nên
// hợp đồng trộn hai cách khai mất tiêu chí ở giữa — đúng lớp «bên đọc hẹp
// hơn bên viết» mà chính bộ bóc này sinh ra để đóng.
const gach = parseAC(l);
if (gach) { chot(); out.push(gach); continue; }
if (cur) { cur.than.push(l); continue; }
}
chot();
return out;
}

module.exports = { AC_LINE, AC_SUSPECT, AC_XREF, AC_HEAD, CRITERIA_HEADINGS, criteriaLines, criteriaLinesScoped, parseAC, parseACBlock, acBlindSpot, blindSpotText };
Loading