@educa-corp/sdd-framework 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/lint-trace.js
CHANGED
|
@@ -681,6 +681,57 @@ function expandImplements(value) {
|
|
|
681
681
|
return out;
|
|
682
682
|
}
|
|
683
683
|
|
|
684
|
+
/**
|
|
685
|
+
* Thu mọi `@trace.implements` của một file, XỬ ĐƯỢC tag trải nhiều dòng.
|
|
686
|
+
*
|
|
687
|
+
* Vì sao cần (phát hiện 2026-08-27, ngay lượt dùng T14 đầu tiên trên dữ liệu thật):
|
|
688
|
+
* quy ước thực tế viết danh sách dài xuống nhiều dòng comment —
|
|
689
|
+
*
|
|
690
|
+
* // @trace.verifies=LESS-06-UC1-SC1, LESS-06-UC1-SC2, LESS-06-UC1-SC3,
|
|
691
|
+
* // LESS-06-UC1-SC4, LESS-06-UC1-SC5, LESS-06-UC1-SC9,
|
|
692
|
+
*
|
|
693
|
+
* Bản đầu của T14 đọc theo TỪNG DÒNG nên chỉ thấy 3 SC đầu và bỏ hết phần sau. Nó sai
|
|
694
|
+
* theo hướng an toàn (báo thiếu, không báo oan) nhưng vẫn là mù — và mù đúng ở những UC
|
|
695
|
+
* có nhiều scenario nhất, tức đúng chỗ cần nhìn nhất.
|
|
696
|
+
*
|
|
697
|
+
* Luật nối dòng: giá trị kết thúc bằng `,` ⇒ đọc tiếp các dòng sau **chỉ khi** dòng đó
|
|
698
|
+
* thuần là danh sách id (cho phép tiền tố comment). Gặp dòng khác ⇒ dừng. Chặt như vậy để
|
|
699
|
+
* một dấu phẩy cuối câu văn xuôi không kéo cả đoạn prose vào làm sc_id rác.
|
|
700
|
+
*/
|
|
701
|
+
const ID_LIST_LINE = /^\s*(?:\/{2,}|\/\*+|\*|#)?\s*(?:[A-Z0-9][A-Z0-9-]*-UC\d+-(?:SC|BR|AC)\d+|(?:SC|BR|AC)\d+)\s*(?:,\s*(?:[A-Z0-9][A-Z0-9-]*-UC\d+-(?:SC|BR|AC)\d+|(?:SC|BR|AC)\d+)\s*)*,?\s*(?:\*\/)?\s*$/;
|
|
702
|
+
|
|
703
|
+
function collectImplements(txt) {
|
|
704
|
+
const out = [];
|
|
705
|
+
const lines = txt.split(/\r?\n/);
|
|
706
|
+
for (let i = 0; i < lines.length; i++) {
|
|
707
|
+
// matchAll, KHÔNG exec: một dòng comment có thể mang NHIỀU tag ngăn bởi `·`, và
|
|
708
|
+
// `exec` trên regex không-global chỉ trả về match ĐẦU TIÊN ⇒ mất tag thứ hai.
|
|
709
|
+
//
|
|
710
|
+
// Và group phải DỪNG trước `@trace.` kế tiếp (negative lookahead). Không có nó thì
|
|
711
|
+
// group tham lam nuốt luôn tag thứ hai, `matchAll` chỉ thấy MỘT match, và tag sau bị
|
|
712
|
+
// mất trong im lặng — bug từ bản đầu của T14: bản đó chỉ CẮT giá trị cho khỏi sinh
|
|
713
|
+
// sc_id rác, nó không bao giờ LẤY LẠI tag bị nuốt.
|
|
714
|
+
const hits = [...lines[i].matchAll(/@trace\.implements\s*[=:]?\s*((?:(?!@trace\.)[^\n\r*])*)/g)];
|
|
715
|
+
if (!hits.length) continue;
|
|
716
|
+
let j = i + 1;
|
|
717
|
+
hits.forEach((m, k) => {
|
|
718
|
+
// Lookahead ở trên đã chặn tag kế tiếp; `·` và khoảng trắng dư do expandImplements bỏ.
|
|
719
|
+
let buf = m[1];
|
|
720
|
+
// CHỈ tag cuối cùng của dòng mới có thể trải tiếp — các tag trước nó đã bị cắt ở
|
|
721
|
+
// `@trace.` nên không bao giờ kết thúc bằng dấu phẩy.
|
|
722
|
+
if (k === hits.length - 1) {
|
|
723
|
+
while (buf.trimEnd().endsWith(',') && j < lines.length && ID_LIST_LINE.test(lines[j])) {
|
|
724
|
+
buf += ' ' + lines[j].replace(/^\s*(?:\/{2,}|\/\*+|\*|#)?\s*/, '').replace(/\*\/\s*$/, '');
|
|
725
|
+
j++;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
out.push(...expandImplements(buf));
|
|
729
|
+
});
|
|
730
|
+
i = j - 1;
|
|
731
|
+
}
|
|
732
|
+
return out;
|
|
733
|
+
}
|
|
734
|
+
|
|
684
735
|
function lintCodeVsLedger() {
|
|
685
736
|
if (!CODE_DIRS.length) {
|
|
686
737
|
infos.push(`T14 BỎ QUA — không có --code nên chưa đối chiếu code với sổ. ` +
|
|
@@ -715,18 +766,11 @@ function lintCodeVsLedger() {
|
|
|
715
766
|
try { txt = fs.readFileSync(p, 'utf8'); } catch { continue; }
|
|
716
767
|
if (!txt.includes('@trace.implements')) continue;
|
|
717
768
|
filesScanned++;
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
if (cut >= 0) v = v.slice(0, cut);
|
|
724
|
-
for (const { uc, unit } of expandImplements(v)) {
|
|
725
|
-
if (!unit.startsWith('SC')) continue; // BR/AC là tag hợp lệ, không phải scenario
|
|
726
|
-
const id = `${uc}-${unit}`;
|
|
727
|
-
if (!tagged.has(id)) tagged.set(id, new Set());
|
|
728
|
-
tagged.get(id).add(rel(p));
|
|
729
|
-
}
|
|
769
|
+
for (const { uc, unit } of collectImplements(txt)) {
|
|
770
|
+
if (!unit.startsWith('SC')) continue; // BR/AC là tag hợp lệ, không phải scenario
|
|
771
|
+
const id = `${uc}-${unit}`;
|
|
772
|
+
if (!tagged.has(id)) tagged.set(id, new Set());
|
|
773
|
+
tagged.get(id).add(rel(p));
|
|
730
774
|
}
|
|
731
775
|
}
|
|
732
776
|
};
|
package/bin/trace-schema.json
CHANGED
|
@@ -1855,6 +1855,11 @@
|
|
|
1855
1855
|
]
|
|
1856
1856
|
},
|
|
1857
1857
|
"forbidden_patterns": [
|
|
1858
|
+
{
|
|
1859
|
+
"pattern": "`integration`",
|
|
1860
|
+
"reason": "Giá trị cột `fe_phase` là `integrated` (CÓ hậu tố -ed). Dạng không -ed chỉ thuộc về CỜ `--phase=integration`. Ghi sai vào sổ thì `--lint-trace` T3 báo lỗi enum, VÀ `fe_integrated` ở /validate-traces Step 7 đếm `integrated` nên counter mãi bằng 0 trong im lặng — đúng hình dạng G1 (cờ im lặng). R6 ĐÃ DỰ ĐOÁN ca này (comment của nó: 'G19 là ca này') và chỉ đúng công cụ là forbidden_patterns — nhưng chưa ai khai. Nó tái diễn thật ở 0.9.0: bốn chỗ trong hai lệnh ghi giá trị cột là dạng không -ed, và CHÍNH validate-traces.tmpl tự mâu thuẫn: một dòng bảo ghi dạng đó, dòng khác đếm `integrated`.",
|
|
1861
|
+
"gap": "G19/G61"
|
|
1862
|
+
},
|
|
1858
1863
|
{
|
|
1859
1864
|
"pattern": "bdd/{UC-ID}.feature",
|
|
1860
1865
|
"reason": "@trace.source thiếu segment {platform}. Layout thật: bdd/{platform}/{UC-ID}-{slug}.feature",
|
package/core/FRAMEWORK_VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.9.
|
|
1
|
+
0.9.1
|
|
@@ -776,7 +776,8 @@ Cập nhật `{paths.trace_dir}/{domain}/{prd-slug}/{UC-ID}-{@trace.platform}.ts
|
|
|
776
776
|
| `bdd_version` | `@trace.bdd_version` từ header `.feature` |
|
|
777
777
|
| `tech_doc_revision` | `@trace.revision` từ tech-doc gộp `{TICKET-ID}-tech-design.md` (§4 backend đã điều khiển codegen của UC này), hoặc `—` nếu chưa có doc |
|
|
778
778
|
| `fe_tech_doc_revision` | `@trace.revision` của cùng tech-doc gộp, ghi khi sinh FE có wire adapter theo §4.5.4 (`--phase=integration` **hoặc** `fe_full`); `—` cho BE, hoặc cho FE `--phase=ui` / chưa có §4.5.4 |
|
|
779
|
-
| `fe_phase` | `ui` nếu `--phase=ui` \|
|
|
779
|
+
| `fe_phase` | `ui` nếu `--phase=ui` \| **`integrated`** nếu `--phase=integration` **hoặc** `fe_full` (đều đã wire real adapter) \| `—` cho BE |
|
|
780
|
+
> ⚠️ Giá trị cột là **`integrated`** — có hậu tố **-ed**. Dạng không -ed chỉ thuộc về **cờ** `--phase=integration`, không bao giờ là giá trị của cột; vocabulary của cột chỉ có `ui` \| `integrated` (`trace-schema.json` → `vocabularies.fe_phase`). Ghi sai giá trị thì `--lint-trace` T3 báo lỗi, và `fe_integrated` ở `/validate-traces` Step 7 đếm `integrated` nên sẽ **mãi bằng 0 trong im lặng**.
|
|
780
781
|
| `last_updated` | hôm nay `YYYY-MM-DD` |
|
|
781
782
|
|
|
782
783
|
Giữ nguyên mọi cột khác (`sc_title`, `spec_ver`, `prd_version`, `prd_status`, `uc_status`, `test_count`, `test_classes`, `dev_selftest`, `dev_selftest_at`, `qc_status`, `qc_run_at`, `qc_owner`, `qc_blocked_by`) — **trừ ngoại lệ có kiểm soát ngay dưới đây**: khi logic vừa đổi thật (lấp stub, hoặc sửa method vì `DRIFT`), 4 cột nghiệm thu `dev_selftest`/`dev_selftest_at`/`qc_status`/`qc_run_at` **phải bị hạ** về "chưa biết". Giữ một `pass` đã hết hiệu lực là báo cáo sai, không phải tôn trọng quyền sở hữu cột.
|
|
@@ -908,7 +909,7 @@ Output Artifacts · Next) cho report cuối, kèm khối bên dưới.
|
|
|
908
909
|
Files: created={N}, extended={M}, filled={F} stub, skipped={K} | Build: SUCCESS
|
|
909
910
|
Branch: feature/{TICKET_ID}-{slug}
|
|
910
911
|
Phase : {UI (mock layer) | Integration (real API) | FE full (UI + real API) | BE full}
|
|
911
|
-
fe_phase : {ui |
|
|
912
|
+
fe_phase : {ui | integrated (--phase=integration | fe_full) | —}
|
|
912
913
|
Figma : {Dev Mode MCP local (grounded) | ⚠️ chỉ link web + text spec (không có MCP local) | n/a cho BE} ← chỉ UI FE/App
|
|
913
914
|
|
|
914
915
|
Next:
|
|
@@ -190,6 +190,30 @@ Nhiều cờ scope cùng lúc → **DỪNG**, báo lỗi: chúng loại trừ nh
|
|
|
190
190
|
|
|
191
191
|
`--prd`/`--uc` không phân giải được về một package/UC có thật → **DỪNG** và liệt kê ứng viên. **KHÔNG** âm thầm rơi về `all` (chạy toàn bộ khi người ta xin một phần là đốt 30 phút không ai muốn) và **KHÔNG** âm thầm audit rỗng (báo cáo "sạch" trên 0 row).
|
|
192
192
|
|
|
193
|
+
**Token TRẦN (không mở đầu bằng `--`)** — vd `/validate-traces --reconcile-code LESS-06`:
|
|
194
|
+
|
|
195
|
+
| Token trần trông như | Xử |
|
|
196
|
+
|---|---|
|
|
197
|
+
| **UC-ID** (`{TICKET}-UC{n}`) | coi là `--uc {token}` |
|
|
198
|
+
| **TICKET-ID** phân giải được về đúng **một** feature-package | coi là `--prd {token}` |
|
|
199
|
+
| không phân giải được, hoặc khớp **nhiều** package | **DỪNG**, liệt kê ứng viên |
|
|
200
|
+
| có **cả** token trần **và** một cờ scope | **DỪNG** — hai nguồn phạm vi, không tự chọn hộ |
|
|
201
|
+
|
|
202
|
+
Hai hàng đầu **phải in một dòng** nói rõ đã tự suy, để người dùng thấy mình vừa được hiểu thế nào:
|
|
203
|
+
```
|
|
204
|
+
ⓘ Hiểu `{token}` là `--prd {token}` ({domain}/{prd-slug}). Gõ cờ tường minh nếu ý khác.
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
> **Vì sao không để token trần rơi về `all` (phát hiện 2026-08-27, lượt dùng `--reconcile-code` đầu tiên).**
|
|
208
|
+
> Gate Bước 1 loại mọi token `--` khỏi phần resolve target, và lệnh này **không có target file** — nên
|
|
209
|
+
> token trần trước đây **bị bỏ hoàn toàn** ⇒ `scope` = `all`. Người dùng gõ `--reconcile-code LESS-06`
|
|
210
|
+
> tin rằng đang chạy hẹp một PRD, trong khi lệnh sẽ **GHI vào sổ của cả 20+ PRD**, gồm cả phần của PO
|
|
211
|
+
> khác. Đây đúng điều chính step này cấm hai dòng ở trên — *"KHÔNG âm thầm rơi về `all`"* — chỉ khác là
|
|
212
|
+
> lỗ vào bằng cửa *"không có cờ nào"* thay vì cửa *"cờ không phân giải được"*.
|
|
213
|
+
>
|
|
214
|
+
> Với một lệnh **chỉ đọc** thì đó là đốt thời gian. Với `--reconcile-code` (có GHI) thì đó là ghi ra
|
|
215
|
+
> ngoài phạm vi mà người dùng nghĩ mình đã yêu cầu — hạng nặng hơn hẳn.
|
|
216
|
+
|
|
193
217
|
Lưu `scope` — mọi step sau dùng nó:
|
|
194
218
|
|
|
195
219
|
| Step | Hẹp thế nào |
|
|
@@ -621,7 +645,7 @@ Không đụng `dev_selftest`/`qc_status` (luật G28 không áp: **không có l
|
|
|
621
645
|
|---|---|
|
|
622
646
|
| `implemented_by` | tên class/hàm mang `@trace.implements` cho SC đó (nhiều file → nối bằng `,`, đúng khuôn `/generate-code` đang ghi) |
|
|
623
647
|
| `gen_ver` | copy `spec_ver` của **chính row đó** *(không phải version hiện tại của `.feature` — code sinh ra từ bản nào thì `spec_ver` lúc này là bản đó; Step 2 vừa đồng bộ nó)* |
|
|
624
|
-
| `fe_phase` |
|
|
648
|
+
| `fe_phase` | **`integrated`** nếu file code có wire adapter thật theo §4.5.4 · `ui` nếu còn `@trace.mock_for`/mock adapter · `—` cho `system`. **Không suy được** → để `—`, đừng đoán. *(Vocabulary chỉ có `ui` \| `integrated` — nhớ hậu tố **-ed**. Dạng không -ed thuộc về cờ `--phase=integration`, không phải cột này.)* |
|
|
625
649
|
| `test_count` · `test_classes` | **CHỈ khi `has_verifies` = true** (Step 2b đã thu). Số test + tên class lấy từ file mang `@trace.verifies`. `has_verifies` = false → **để trống**, tuyệt đối không điền 0-giả hay tên class phỏng đoán |
|
|
626
650
|
|
|
627
651
|
> ### ⚠️ Luật cứng: KHÔNG set `status`
|
|
@@ -654,7 +678,7 @@ Không đụng `dev_selftest`/`qc_status` (luật G28 không áp: **không có l
|
|
|
654
678
|
{UC-ID}-{platform}.tsv:{dòng} {sc_id}
|
|
655
679
|
implemented_by ← {ClassName.method} (từ {file}:{dòng})
|
|
656
680
|
gen_ver ← {spec_ver}
|
|
657
|
-
fe_phase ← {ui |
|
|
681
|
+
fe_phase ← {ui | integrated | —}
|
|
658
682
|
test_count ← {n} · test_classes ← {…} (từ @trace.verifies ở {file}) [hoặc: bỏ trống — không có @trace.verifies]
|
|
659
683
|
```
|
|
660
684
|
Ghi bù im lặng là ghi bù không kiểm chứng được.
|