@educa-corp/sdd-framework 0.7.0 → 0.7.2
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 +64 -3
- package/bin/self-check.js +8 -5
- package/bin/trace-schema.json +44 -4
- package/core/FRAMEWORK_VERSION +1 -1
- package/core/commands/amend-prd.md +3 -2
- package/core/commands/dev-run-test.md +1 -1
- package/core/commands/generate-bdd.md +9 -6
- package/core/commands/generate-code.md +2 -2
- package/core/commands/generate-tech-docs.md +2 -2
- package/core/commands/map-testids.md +1 -1
- package/core/commands/validate-traces.md +6 -6
- package/core/steps/context-loader.md +1 -1
- package/core/templates/ci/trace-gate.gitlab-ci.yml +169 -0
- package/core/templates/project-context.yaml +3 -3
- package/core/templates/tech-design.template.md +2 -2
- package/docs/02-concepts/architecture.md +1 -1
- package/docs/02-concepts/overview.md +1 -1
- package/docs/02-concepts/pipeline-steps/02-specification.md +13 -7
- package/docs/02-concepts/pipeline-steps/07-dev-selftest.md +2 -0
- package/docs/02-concepts/pipeline-steps/08-qc-automation.md +1 -0
- package/docs/02-concepts/pipeline-steps/09-validate-traces.md +34 -3
- package/docs/02-concepts/pipeline-steps/10-feedback-loop.md +10 -1
- package/docs/02-concepts/traceability.md +187 -183
- package/docs/03-guides/architect.md +15 -5
- package/docs/03-guides/developer.md +1 -0
- package/docs/03-guides/product-owner.md +89 -72
- package/docs/03-guides/tester-qa.md +81 -81
- package/docs/04-reference/commands.md +1 -1
- package/docs/04-reference/trace-schema.md +8 -1
- package/docs/explain/02c-amend-prd.md +1 -1
- package/docs/explain/06-generate-bdd.md +1 -1
- package/docs/explain/13-dev-run-test.md +15 -1
- package/docs/explain/19-qc-run-test.md +91 -87
- package/docs/explain/21-validate-traces.md +79 -75
- package/package.json +1 -1
package/bin/lint-trace.js
CHANGED
|
@@ -155,6 +155,63 @@ function readLines(file) {
|
|
|
155
155
|
return fs.readFileSync(file, 'utf8').replace(/\r\n/g, '\n').split('\n');
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
// ── T13 — hai điều kiện bảo vệ sổ có KIỂM ĐƯỢC không ─────────────────────────
|
|
159
|
+
//
|
|
160
|
+
// T10 (luật merge) và T11 (bị gitignore) đều hỏi `git`. Trước T13, khi không hỏi được
|
|
161
|
+
// thì cả hai `catch { return; }` — IM LẶNG. Và im lặng ở đây tệ hơn nó nghe: gate-trace
|
|
162
|
+
// G0 lọc warns theo rule id, không thấy gì thì in ra
|
|
163
|
+
// "✅ G0 sổ được bảo vệ — nằm trong git · git biết cách gộp (2/2 điều kiện)"
|
|
164
|
+
// tức KHẲNG ĐỊNH sổ được bảo vệ trên bằng chứng chưa bao giờ được thu. Cùng hình dạng
|
|
165
|
+
// G55 (`pass` không có quyền khẳng định).
|
|
166
|
+
//
|
|
167
|
+
// Ca thường gặp nhất KHÔNG phải hiếm: image CI tối giản. `node:20-alpine` và
|
|
168
|
+
// `node:20-slim` đều KHÔNG có git — tức chính môi trường mà cổng này tồn tại để chạy.
|
|
169
|
+
//
|
|
170
|
+
// Ba trạng thái, phân biệt có chủ ý:
|
|
171
|
+
// ok → hỏi được, T10/T11 chạy bình thường
|
|
172
|
+
// no-binary → không có `git` ⇒ KHÔNG BIẾT sổ có được bảo vệ hay không
|
|
173
|
+
// not-a-repo → có git nhưng sổ không nằm trong repo nào ⇒ nó KHÔNG được commit
|
|
174
|
+
// Hai trạng thái sau đều phát T13. Gate nâng T13 thành chặn (schema →
|
|
175
|
+
// gate.config_preconditions.promote_lint_rules) — pre-push NHẮC, CI CHẶN, y như T10/T11.
|
|
176
|
+
// Thụt lề cho dòng hint nhiều dòng — trùng khuôn của err()/warn().
|
|
177
|
+
const NL = String.fromCharCode(10) + " ";
|
|
178
|
+
let _gitState = null;
|
|
179
|
+
function gitState(sampleFile) {
|
|
180
|
+
if (_gitState) return _gitState;
|
|
181
|
+
const cp = require('child_process');
|
|
182
|
+
const q = (args, opts) => cp.spawnSync('git', args, { encoding: 'utf8', stdio: ['ignore', 'ignore', 'ignore'], ...opts });
|
|
183
|
+
const v = q(['--version']);
|
|
184
|
+
if (v.error || v.status !== 0) return (_gitState = 'no-binary');
|
|
185
|
+
const dir = sampleFile ? path.dirname(sampleFile) : '.';
|
|
186
|
+
const r = q(['rev-parse', '--git-dir'], { cwd: fs.existsSync(dir) ? dir : '.' });
|
|
187
|
+
if (r.error || r.status !== 0) return (_gitState = 'not-a-repo');
|
|
188
|
+
return (_gitState = 'ok');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function lintGitEvaluable(sampleFile) {
|
|
192
|
+
const st = gitState(sampleFile);
|
|
193
|
+
if (st === 'ok') return true;
|
|
194
|
+
const where = rel(sampleFile || TRACE_DIRS[0]);
|
|
195
|
+
if (st === 'no-binary') {
|
|
196
|
+
warn('T13', where,
|
|
197
|
+
'KHÔNG kiểm được điều kiện bảo vệ sổ — môi trường này không có binary `git`',
|
|
198
|
+
['T10 (luật merge) và T11 (bị gitignore) đều phải hỏi git. Không hỏi được thì hai điều',
|
|
199
|
+
'kiện đó KHÔNG được đánh giá — và "không biết" KHÔNG PHẢI "sạch".',
|
|
200
|
+
'Ca thường gặp nhất: image CI tối giản. node:*-alpine và node:*-slim đều KHÔNG có git.',
|
|
201
|
+
'Sửa: image: node:20 (bản Debian — có git sẵn)',
|
|
202
|
+
' hoặc apk add --no-cache git / apt-get install -y git'].join(NL));
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
warn('T13', where,
|
|
206
|
+
'sổ trace KHÔNG nằm trong một repo git nào — nó không được version control',
|
|
207
|
+
['Sổ trace phải commit: nó không regenerate được, và trace-history.jsonl là dữ liệu',
|
|
208
|
+
'tích luỹ — mất là mất vĩnh viễn.',
|
|
209
|
+
'Nếu đây là bản copy tạm để kiểm thì bỏ qua được. Trong CI thì KHÔNG: nghĩa là repo',
|
|
210
|
+
'chưa được clone đúng, hoặc sổ nằm trong submodule chưa clone',
|
|
211
|
+
'(GitLab: GIT_SUBMODULE_STRATEGY=recursive · GitHub: submodules: recursive).'].join(NL));
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
|
|
158
215
|
// ── T7 — marker conflict git ──────────────────────────────────────────────────
|
|
159
216
|
//
|
|
160
217
|
// Chạy TRƯỚC mọi rule khác và trên MỌI file: một marker lọt vào TSV làm T1/T2 báo
|
|
@@ -587,8 +644,12 @@ for (const dir of TRACE_DIRS) {
|
|
|
587
644
|
}
|
|
588
645
|
}
|
|
589
646
|
|
|
590
|
-
|
|
591
|
-
|
|
647
|
+
// T13 TRƯỚC T10/T11: nếu không hỏi được git thì hai rule kia không có gì để phán, và
|
|
648
|
+
// im lặng ở đó làm gate G0 khẳng định "sổ được bảo vệ" trên bằng chứng không tồn tại.
|
|
649
|
+
if (lintGitEvaluable(sampleForAttr || abs)) {
|
|
650
|
+
if (sampleForAttr) lintMergeAttr(sampleForAttr);
|
|
651
|
+
lintGitTracked(abs);
|
|
652
|
+
}
|
|
592
653
|
}
|
|
593
654
|
|
|
594
655
|
// ── Report ────────────────────────────────────────────────────────────────────
|
|
@@ -596,7 +657,7 @@ for (const dir of TRACE_DIRS) {
|
|
|
596
657
|
if (AS_JSON) {
|
|
597
658
|
console.log(JSON.stringify({
|
|
598
659
|
ok: errors.length === 0,
|
|
599
|
-
scanned, trace_dirs: foundDirs, errors, warns, infos,
|
|
660
|
+
scanned, trace_dirs: foundDirs, git_state: _gitState, errors, warns, infos,
|
|
600
661
|
}, null, 2));
|
|
601
662
|
process.exit(errors.length && !WARN_ONLY ? 1 : 0);
|
|
602
663
|
}
|
package/bin/self-check.js
CHANGED
|
@@ -695,13 +695,16 @@ for (const [vocab, def] of Object.entries(schema.vocabularies)) {
|
|
|
695
695
|
{
|
|
696
696
|
const inv = (schema.gate || {}).audit_invalidating_tags;
|
|
697
697
|
if (inv) {
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
698
|
+
// MỖI template CI phải grep đủ 7 tag — không chỉ bản GitHub. Một bản dịch (GitLab…)
|
|
699
|
+
// không được canh mà tụt lại sau schema thì grep không khớp gì ⇒ job LUÔN XANH.
|
|
700
|
+
const rels = inv.ci_templates || (inv.ci_template ? [inv.ci_template] : []);
|
|
701
|
+
if (!rels.length) {
|
|
702
|
+
err('R10', '`gate.audit_invalidating_tags` thiếu `ci_templates` — không biết canh file nào');
|
|
703
|
+
}
|
|
704
|
+
for (const rel of rels) {
|
|
702
705
|
const abs = path.join(ROOT, rel);
|
|
703
706
|
if (!fs.existsSync(abs)) {
|
|
704
|
-
err('R10', `\`audit_invalidating_tags.
|
|
707
|
+
err('R10', `\`audit_invalidating_tags.ci_templates\` trỏ tới ${rel} — file không tồn tại`,
|
|
705
708
|
'Template đổi tên/bị xoá? Không có nó thì lỗ "sửa code mà không audit lại" hở lại.');
|
|
706
709
|
} else {
|
|
707
710
|
const yml = fs.readFileSync(abs, 'utf8');
|
package/bin/trace-schema.json
CHANGED
|
@@ -1107,10 +1107,36 @@
|
|
|
1107
1107
|
"column": "status"
|
|
1108
1108
|
},
|
|
1109
1109
|
"platform": {
|
|
1110
|
+
"$comment": [
|
|
1111
|
+
"PLATFORM = DELIVERY SURFACE, không phải tech stack. `webview` là surface thứ tư có thật:",
|
|
1112
|
+
"một bundle web nhúng trong app native — không phải `web` (browser) và không phải `app`",
|
|
1113
|
+
"(native). Nó có BDD riêng (tương tác khác), design-spec riêng, và repo riêng.",
|
|
1114
|
+
"",
|
|
1115
|
+
"VÌ SAO PHẢI THÊM (báo cáo từ project thật, v0.7.0): framework DẠY `webview` ở BỐN chỗ —",
|
|
1116
|
+
" templates/project-context.yaml FORM C lấy webview mini-game làm ví dụ",
|
|
1117
|
+
" steps/context-loader.md 2c ví dụ đầy đủ: bdd/webview/UC1.feature",
|
|
1118
|
+
" commands/setup-ai-first prompt tự đưa ra `learning:webview:dap-chuot:phaser-game`",
|
|
1119
|
+
" modules/phaser-game stack-profile có mục build cho webview",
|
|
1120
|
+
"— rồi TỪ CHỐI nó ở tầng kiểm. lint-trace T5 là `err`, và validate-traces Step 1.0 DỪNG",
|
|
1121
|
+
"NGAY khi lint exit 1 ⇒ project dùng đúng ví dụ của framework mất TOÀN BỘ audit.",
|
|
1122
|
+
"",
|
|
1123
|
+
"Đổi tên sổ KHÔNG phải đường ra: nó phá khoá routing `by_prd_slug`, lệch với",
|
|
1124
|
+
"@trace.platform trong .feature, và gộp hai surface vào một sổ làm sc_id đụng nhau (mỗi",
|
|
1125
|
+
"platform đánh SC từ 1).",
|
|
1126
|
+
"",
|
|
1127
|
+
"NỢ CÒN LẠI — danh sách này vẫn ĐÓNG: project cần `desktop`/`tv`/`mini-program`/`cli` sẽ",
|
|
1128
|
+
"bị chặn y như vậy và phải chờ một bản release. Nguyên nhân gốc là `platform` được xử lý",
|
|
1129
|
+
"như enum ĐÓNG trong contract nhưng MỌI code path sinh ra nó đều MỞ (setup-ai-first nhận",
|
|
1130
|
+
"chuỗi bất kỳ · context-loader 2b đọc sub-key bất kỳ · generate-bdd ghi bdd/{platform}/",
|
|
1131
|
+
"không kiểm gì). Cách sửa đúng là cho project TỰ KHAI thêm platform trong",
|
|
1132
|
+
"project-context.yaml và để lint-trace đọc — giữ được tính năng bắt typo của T5 mà không",
|
|
1133
|
+
"cần release cho từng surface mới. Đang hoãn có chủ ý, làm ở đợt sau."
|
|
1134
|
+
],
|
|
1110
1135
|
"values": [
|
|
1111
1136
|
"web",
|
|
1112
1137
|
"app",
|
|
1113
|
-
"system"
|
|
1138
|
+
"system",
|
|
1139
|
+
"webview"
|
|
1114
1140
|
],
|
|
1115
1141
|
"filename_segment": "{UC-ID}-{platform}.tsv",
|
|
1116
1142
|
"$filename_note": "Tên sổ là nơi DUY NHẤT mang platform của row — validate-traces Step 2 lấy platform từ đây để tìm .feature đúng thư mục con. Sổ thiếu đoạn này thì cả sổ vô hình (đúng hình dạng G3/G8)."
|
|
@@ -1233,6 +1259,10 @@
|
|
|
1233
1259
|
"Cùng cách phân vai G39 đã chọn cho pre-push vs CI."
|
|
1234
1260
|
],
|
|
1235
1261
|
"promote_lint_rules": [
|
|
1262
|
+
{
|
|
1263
|
+
"rule": "T13",
|
|
1264
|
+
"why": "KHÔNG KIỂM ĐƯỢC hai điều kiện kia — thiếu binary `git`, hoặc sổ không nằm trong repo git nào. Đây là loại THỨ TƯ, khác cả ba loại còn lại: không phải 'đang có cái sai' (🔴), không phải 'nợ' (🟠), cũng không phải 'bảo vệ đang tắt' (T10/T11) — mà là 'KHÔNG BIẾT bảo vệ có bật hay không'. Trước T13, ca này làm G0 in ra `sổ được bảo vệ — 2/2 điều kiện`: một lời KHẲNG ĐỊNH trên bằng chứng chưa bao giờ được thu, tệ hơn im lặng (cùng hình dạng G55). Ca thường gặp nhất là CI dùng image tối giản — node:*-alpine và node:*-slim đều KHÔNG có git — tức chính môi trường mà cổng này tồn tại để chạy."
|
|
1265
|
+
},
|
|
1236
1266
|
{
|
|
1237
1267
|
"rule": "T11",
|
|
1238
1268
|
"why": "sổ trace đang bị gitignore — nó KHÔNG được lưu vào git, mất là mất toàn bộ (G29)"
|
|
@@ -1343,7 +1373,17 @@
|
|
|
1343
1373
|
"self-check R10 canh templates/ci/trace-gate.yml có nhắc đủ 7 tag này. Tag đổi tên mà",
|
|
1344
1374
|
"file CI không biết ⇒ grep không khớp gì ⇒ job LUÔN XANH ⇒ cổng mù im lặng."
|
|
1345
1375
|
],
|
|
1346
|
-
"
|
|
1376
|
+
"$ci_templates_comment": [
|
|
1377
|
+
"MỖI file CI phải grep đủ 7 tag. Trước v0.7.2 chỉ có bản GitHub Actions được canh —",
|
|
1378
|
+
"nên khi một team dịch sang GitLab, bản dịch KHÔNG được rule nào canh, và nếu nó tụt",
|
|
1379
|
+
"lại sau schema thì grep không khớp gì ⇒ job LUÔN XANH ⇒ cổng mù trong im lặng. Đúng",
|
|
1380
|
+
"lỗi R10 tồn tại để chặn, chỉ ở một file khác loại. Thêm template CI mới thì THÊM VÀO",
|
|
1381
|
+
"danh sách này ngay — R10 canh từng file."
|
|
1382
|
+
],
|
|
1383
|
+
"ci_templates": [
|
|
1384
|
+
"templates/ci/trace-gate.yml",
|
|
1385
|
+
"templates/ci/trace-gate.gitlab-ci.yml"
|
|
1386
|
+
],
|
|
1347
1387
|
"tags": [
|
|
1348
1388
|
{
|
|
1349
1389
|
"tag": "@trace.implements",
|
|
@@ -1614,8 +1654,8 @@
|
|
|
1614
1654
|
"khớp sổ ⇒ PASS · require-fresh-audit thấy PR không chạm tag ⇒ không đòi audit. Cả ba",
|
|
1615
1655
|
"tầng xanh, và cả ba ĐÚNG theo định nghĩa của chính chúng.",
|
|
1616
1656
|
"",
|
|
1617
|
-
"Và G54 không chỉ CHO PHÉP sửa tay — nó ĐẨY người dùng về đó: /generate-prd
|
|
1618
|
-
"
|
|
1657
|
+
"Và G54 không chỉ CHO PHÉP sửa tay — nó ĐẨY người dùng về đó: /generate-prd DỪNG HẲN trên",
|
|
1658
|
+
"file đã có (§Guard: 'Tồn tại → DỪNG. KHÔNG ghi, KHÔNG hỏi Y/N') · /extend-prd chỉ",
|
|
1619
1659
|
"add-only · /refine-prd chỉ áp được finding do chính nó tìm ra. Trước /amend-prd, hành",
|
|
1620
1660
|
"vi hợp lý duy nhất còn lại LÀ mở file .md ra gõ. Thiết kế tự tạo ra con đường nó không",
|
|
1621
1661
|
"canh được.",
|
package/core/FRAMEWORK_VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.7.
|
|
1
|
+
0.7.2
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
> | **PRD đã có, PO muốn ĐỔI một yêu cầu đang đúng cú pháp** | **`/amend-prd`** | **Edit tại chỗ** — output **KHÔNG** phải superset |
|
|
11
11
|
>
|
|
12
12
|
> **Vì sao phải là lệnh riêng (GAPS-v4 G54).** Ba lệnh kia đều **từ chối đúng việc này**:
|
|
13
|
-
> `/generate-prd`
|
|
14
|
-
>
|
|
13
|
+
> `/generate-prd` **DỪNG HẲN** trên file đã có (§Guard: *"Tồn tại → DỪNG. KHÔNG ghi, KHÔNG hỏi
|
|
14
|
+
> Y/N"* — vì ghi đè mất changelog, **đánh số lại BR**, phá `@trace.business_rules` trong mọi
|
|
15
|
+
> `.feature` đã sinh, cả ba không hoàn tác được) · `/extend-prd` chỉ **add-only**, luật
|
|
15
16
|
> Bước 5 §3 đòi output là *"superset chặt"* · `/refine-prd` tự cấm đụng section nào không được một
|
|
16
17
|
> finding trỏ tới, và findings sinh từ việc soi PRD hiện có nên **không có đường nào để một ý định
|
|
17
18
|
> MỚI của PO đi vào**.
|
|
@@ -338,7 +338,7 @@ Sau khi chạy, lưu kết quả vào **TSV authoritative** trong service để
|
|
|
338
338
|
report Living Docs ở spec module (qua `/sync` + `/validate-traces`). Các file test
|
|
339
339
|
ở lại trong service — chỉ *status* của lần chạy được report.
|
|
340
340
|
|
|
341
|
-
Cập nhật **sổ của platform đang test** `{paths.trace_dir}/{domain}/{prd-slug}/{UC-ID}-{platform}.tsv` (`{platform}` = platform của code/`.feature` đang test — `system` cho backend, `web`/`app
|
|
341
|
+
Cập nhật **sổ của platform đang test** `{paths.trace_dir}/{domain}/{prd-slug}/{UC-ID}-{platform}.tsv` (`{platform}` = platform của code/`.feature` đang test — `system` cho backend, mọi platform khác (`web`/`app`/`webview`/…) cho client; nếu `domain`/`prd_slug` không phân giải được từ spec target, định vị TSV bằng cách glob `{paths.trace_dir}/**/{UC-ID}-{platform}.tsv` — nó được tạo trước đó bởi `/generate-bdd`) — cho mỗi scenario row (khớp `sc_id` qua tag
|
|
342
342
|
`@trace.verifies={UC-ID}-SC{N}` của test). *(Umbrella + `spec_source`: `trace_dir` là `{spec_source}/.trace` — test chạy từ `service_root` nhưng update `dev_selftest` ghi vào **spec repo**; commit/push spec submodule cho nó.)*
|
|
343
343
|
|
|
344
344
|
### ĐỌC cột `status` của row TRƯỚC KHI GHI *(bắt buộc)*
|
|
@@ -222,11 +222,14 @@ Hỏi người dùng chọn platform target:
|
|
|
222
222
|
|
|
223
223
|
```
|
|
224
224
|
BDD này dành cho platform nào?
|
|
225
|
-
1. web
|
|
226
|
-
2. app
|
|
227
|
-
3.
|
|
225
|
+
1. web — FE/Web trong browser (React, Next.js, Angular, Vue, Nuxt)
|
|
226
|
+
2. app — Mobile native (Flutter, React Native, iOS, Android)
|
|
227
|
+
3. webview — Bundle web NHÚNG trong app native (Phaser game, mini-app)
|
|
228
|
+
4. system — System/BE BDD (tổng hợp từ BDD client có sẵn)
|
|
228
229
|
```
|
|
229
230
|
|
|
231
|
+
*Chỉ hiện những platform project thực sự dùng: nếu `services.{domain}` là map-theo-platform (context-loader 2b) thì lấy đúng các sub-key của nó làm danh sách; ngược lại hiện đủ bốn. `webview` là một **delivery surface** riêng — không phải `web` (browser) và không phải `app` (native) — nên nó có BDD, design-spec và sổ trace riêng.*
|
|
232
|
+
|
|
230
233
|
Chờ người dùng chọn. Set `active_platform` = giá trị đã chọn.
|
|
231
234
|
|
|
232
235
|
**Output path (spec repo mode):**
|
|
@@ -244,7 +247,7 @@ Chờ người dùng chọn. Set `active_platform` = giá trị đã chọn.
|
|
|
244
247
|
|
|
245
248
|
## System BDD Synthesis (active_platform = system)
|
|
246
249
|
|
|
247
|
-
*Chỉ áp dụng khi platform = system
|
|
250
|
+
*Chỉ áp dụng khi platform = `system`. Bỏ qua với mọi platform client (`web`/`app`/`webview`/…).*
|
|
248
251
|
|
|
249
252
|
### Step S0 — Brownfield Check
|
|
250
253
|
|
|
@@ -439,7 +442,7 @@ Umbrella mode không hỏi platform (khác spec repo mode) — nó **suy** từ
|
|
|
439
442
|
|
|
440
443
|
## Design Spec — Gate & Load (chỉ FE/App)
|
|
441
444
|
|
|
442
|
-
*Chỉ chạy khi target platform là FE/App — spec mode: `active_platform
|
|
445
|
+
*Chỉ chạy khi target platform là FE/App — spec mode: `active_platform` là platform client (mọi giá trị **trừ** `system` — `web`, `app`, `webview`, `app-ios`, `app-android`, …); umbrella mode: `active_module` là module FE/App (react/nextjs/vue/nuxt/angular/flutter/react-native/ios-swiftui/android-compose). Bỏ qua HOÀN TOÀN với `system` và backend/brownfield.*
|
|
443
446
|
|
|
444
447
|
**1. Định vị design-spec của platform:**
|
|
445
448
|
`{paths.specs_dir}/{domain}/{prd-slug}/design-spec/{TICKET-ID}-design-spec-{active_platform}-{slug}.md`
|
|
@@ -795,7 +798,7 @@ In danh sách SC được bump vào report cuối để người dùng biết c
|
|
|
795
798
|
|
|
796
799
|
## Write Trace State
|
|
797
800
|
|
|
798
|
-
Sau khi sinh tất cả file `.feature`, tạo hoặc cập nhật **sổ trace theo platform** `{paths.trace_dir}/{domain}/{prd-slug}/{UC-ID}-{active_platform}.tsv` cho mỗi UC — một sổ riêng cho `system` / `web` / `app
|
|
801
|
+
Sau khi sinh tất cả file `.feature`, tạo hoặc cập nhật **sổ trace theo platform** `{paths.trace_dir}/{domain}/{prd-slug}/{UC-ID}-{active_platform}.tsv` cho mỗi UC — một sổ riêng cho mỗi platform (`system` / `web` / `app` / `webview` / …). Vì `sc_id` = `{UC-ID}-SC{N}` chỉ độc nhất trong (UC × platform) (mỗi platform tự đánh số SC từ 1), **mỗi platform một file** để scenario platform này không đè/xoá platform khác. Lệnh luôn biết `active_platform` (từ Platform Selection / Service Detection) nên chỉ ghi đúng sổ của platform đang gen.
|
|
799
802
|
|
|
800
803
|
> **Umbrella + `spec_source`:** cả file `.feature` **và** trace `.tsv` đều ghi vào **spec repo** (`{spec_source}/specs/{domain}/{prd-slug}/bdd/…` và `{spec_source}/.trace/{domain}/{prd-slug}/…`, do context-loader phân giải) — một thao tác ghi **single-repo**, commit/push vào spec submodule. (Trace được gộp trong spec repo để PM quản lý mọi status ở một chỗ; các lệnh phía code cập nhật liên-repo sau.)
|
|
801
804
|
|
|
@@ -266,7 +266,7 @@ Lệnh này giới hạn nghiêm ngặt trong **một file feature** được tr
|
|
|
266
266
|
|
|
267
267
|
## Phase Detection
|
|
268
268
|
|
|
269
|
-
> **Nguồn chuẩn quyết BE/FE = `@trace.platform` của FILE FEATURE**
|
|
269
|
+
> **Nguồn chuẩn quyết BE/FE = `@trace.platform` của FILE FEATURE** — **`system` → BE · MỌI platform khác → FE** (`web`, `app`, `webview`, và bất kỳ surface nào project khai thêm). Luật viết bằng **phủ định**, không phải liệt kê: liệt kê `web`/`app` làm platform thứ tư không khớp nhánh nào, và lệnh sẽ phải tự đoán — sinh sai loại code mà không cờ nào báo. KHÔNG dùng `platform_type` (suy từ module) để quyết BE/FE — nó chỉ dùng cho **idiom stack/module** (cú pháp, layer, thư viện). Lý do: repo fullstack một-module (vd Next.js có API route) có `platform_type` cố định một giá trị, nhưng vẫn có cả feature `system` (BE) lẫn `web` (FE) — chỉ tag của chính feature mới đúng.
|
|
270
270
|
|
|
271
271
|
Parse `$ARGUMENTS` tìm flag `--phase` và `--force`:
|
|
272
272
|
|
|
@@ -275,7 +275,7 @@ Parse `$ARGUMENTS` tìm flag `--phase` và `--force`:
|
|
|
275
275
|
| `--phase=ui` | FE Phase 1 — sinh UI + layer mock API từ System BDD contract |
|
|
276
276
|
| `--phase=integration` | FE Phase 2 — thay mock adapter bằng lời gọi API thật từ tech docs |
|
|
277
277
|
| `--force` | "Gen lại tường minh" — **CHỈ** bỏ qua guard status ở §Read Trace State (không skip row đang `OK`). Xem định nghĩa hẹp bên dưới. |
|
|
278
|
-
| *(không có)* | Default — full:
|
|
278
|
+
| *(không có)* | Default — full: **`system`** → full backend; **mọi platform khác** (`web`/`app`/`webview`/…) → **FE full** (sinh UI + wire API thật trong một lần, không qua bước mock) |
|
|
279
279
|
|
|
280
280
|
> **`--force` có phạm vi HẸP — đây là ranh giới cứng, không phải khuyến nghị.**
|
|
281
281
|
> Nó bỏ qua **đúng một** thứ: luật "row `OK` thì skip" ở §Read Trace State. **Mọi guard khác giữ nguyên hiệu lực:** Scope Lock (cấm implement scenario của `.feature` khác) · quy tắc EXTEND phi-phá-huỷ (đọc lại trước khi ghi · CẤM full Write trên file đã tồn tại · output phải là superset chặt) · Guard sau-ghi · Fill-before-create · Build Verify.
|
|
@@ -421,7 +421,7 @@ Ghi/mở rộng `{output_path}` dùng template dưới đây, chỉ sinh **nội
|
|
|
421
421
|
@trace.ucs: {TICKET-ID}-UC1, {TICKET-ID}-UC2{, …}
|
|
422
422
|
@trace.service: {service — từ header BDD @trace.service}
|
|
423
423
|
@trace.module: {module liên quan — vd dotnet, angular}
|
|
424
|
-
@trace.platforms: {system | web | app — tuỳ thư mục BDD nào tồn tại}
|
|
424
|
+
@trace.platforms: {system | web | app | webview | … — tuỳ thư mục BDD nào tồn tại}
|
|
425
425
|
@trace.bdd_versions: {MAP theo từng platform — số nhiều, KHÁC @trace.bdd_version (scalar) của .feature — vd system=1.5, web=1.9, app=1.7; chỉ platform có mặt. Mỗi feature mang bdd_version riêng; đừng gộp về một số.}
|
|
426
426
|
@trace.api_source: {existing | —}
|
|
427
427
|
@trace.revision: 1
|
|
@@ -794,7 +794,7 @@ sequenceDiagram
|
|
|
794
794
|
|
|
795
795
|
| UC | Feature | Platforms | Section phủ | Trạng thái |
|
|
796
796
|
|----|---------|-----------|------------------|--------|
|
|
797
|
-
| {TICKET-ID}-UC1 | {title} | {system, web, app} | §… | ✅ Covered |
|
|
797
|
+
| {TICKET-ID}-UC1 | {title} | {system, web, app, webview…} | §… | ✅ Covered |
|
|
798
798
|
|
|
799
799
|
### Độ phủ Scenario UC1
|
|
800
800
|
|
|
@@ -180,7 +180,7 @@ placeholder bên dưới sẽ rỗng và lệnh sẽ đọc/ghi sai chỗ.
|
|
|
180
180
|
|
|
181
181
|
Phân giải `platform` từ `@trace.platform` / `platform_type`. Test-id là chuyện của **FE/App** — nếu `system` / backend → HALT:
|
|
182
182
|
```
|
|
183
|
-
❌ /map-testids chỉ áp dụng cho
|
|
183
|
+
❌ /map-testids chỉ áp dụng cho platform CLIENT (mọi platform trừ `system` — web/app/webview/…). BE không có UI test-id.
|
|
184
184
|
```
|
|
185
185
|
Phân giải attribute test-id từ `@trace.testid_attr` (hoặc theo module): web `data-testid` · React Native `testID` · Flutter `Key`/`Semantics(identifier:)` · native iOS `accessibilityIdentifier`.
|
|
186
186
|
|
|
@@ -828,7 +828,7 @@ Schema:
|
|
|
828
828
|
}
|
|
829
829
|
},
|
|
830
830
|
"by_platform": {
|
|
831
|
-
"<web | app | system
|
|
831
|
+
"<platform — web | app | system | webview | … ; CHỈ platform thực sự có scenario>": {
|
|
832
832
|
"total_scs": 0, "coded_scs": 0, "tested_scs": 0, "drift_count": 0
|
|
833
833
|
}
|
|
834
834
|
}
|
|
@@ -850,7 +850,7 @@ Schema:
|
|
|
850
850
|
"scenarios": [
|
|
851
851
|
{
|
|
852
852
|
"sc_id": "<e.g. PAY-UC01-SC1>",
|
|
853
|
-
"platform": "web | app | system",
|
|
853
|
+
"platform": "web | app | system | webview | …",
|
|
854
854
|
"sc_title": "<title>",
|
|
855
855
|
"spec_ver": "<current version from .feature>",
|
|
856
856
|
"gen_ver": "<version at codegen time>",
|
|
@@ -956,7 +956,7 @@ Schema:
|
|
|
956
956
|
"bdd_drift": [
|
|
957
957
|
{
|
|
958
958
|
"uc_id": "<UC-ID>",
|
|
959
|
-
"platform": "web | app | system",
|
|
959
|
+
"platform": "web | app | system | webview | …",
|
|
960
960
|
"code_bdd_version": "<@trace.bdd_version trong code>",
|
|
961
961
|
"current_bdd_version": "<@trace.bdd_version của .feature>",
|
|
962
962
|
"drifted_scs": ["<SC đang DRIFT của UC này>"],
|
|
@@ -966,7 +966,7 @@ Schema:
|
|
|
966
966
|
"techdoc_stale_vs_bdd": [
|
|
967
967
|
{
|
|
968
968
|
"uc_id": "<UC-ID>",
|
|
969
|
-
"platform": "web | app | system",
|
|
969
|
+
"platform": "web | app | system | webview | …",
|
|
970
970
|
"techdoc_bdd_version": "<entry trong map @trace.bdd_version của tech-doc>",
|
|
971
971
|
"current_bdd_version": "<@trace.bdd_version của .feature>",
|
|
972
972
|
"fix": "/generate-tech-docs <feature-file> then /review-tech-docs"
|
|
@@ -993,7 +993,7 @@ Schema:
|
|
|
993
993
|
"orphaned": [
|
|
994
994
|
{
|
|
995
995
|
"sc_id": "<SC-ID đã bị xoá khỏi .feature>",
|
|
996
|
-
"platform": "web | app | system",
|
|
996
|
+
"platform": "web | app | system | webview | …",
|
|
997
997
|
"implemented_by": "<ClassName.method còn tồn tại>",
|
|
998
998
|
"test_classes": ["<test còn trỏ vào SC này>"],
|
|
999
999
|
"fix": "xoá code + test, HOẶC đưa scenario trở lại .feature"
|
|
@@ -1037,7 +1037,7 @@ Schema:
|
|
|
1037
1037
|
"service_unrouted": [
|
|
1038
1038
|
{
|
|
1039
1039
|
"sc_id": "<SC-ID>",
|
|
1040
|
-
"platform": "web | app | system",
|
|
1040
|
+
"platform": "web | app | system | webview | …",
|
|
1041
1041
|
"domain": "<domain của PRD>",
|
|
1042
1042
|
"value": "unrouted | unresolved",
|
|
1043
1043
|
"reason": "<lý do context-loader đã ghi — vd: domain chưa có entry trong services:>",
|
|
@@ -403,7 +403,7 @@ Dict : {loaded — N canonical terms, M banned terms | missing}
|
|
|
403
403
|
Entities : {loaded — EntityA, EntityB, EntityC | missing}
|
|
404
404
|
Lessons : {loaded — {n} active cho lệnh này ({tổng} tổng) | chưa có}
|
|
405
405
|
{⚠️ CHỈ IN khi tổng ≥ 40: "{tổng} guardrail đang hoạt động — /learn --review để rà"}
|
|
406
|
-
Platform : {active_platform: system | web | app | — nếu chưa xác định}
|
|
406
|
+
Platform : {active_platform: system | web | app | webview | … | — nếu chưa xác định}
|
|
407
407
|
Service : {active_service} ({active_service_module}) [← domain{/platform}{/prd_slug} nếu route qua by_prd_slug] | multi (map-theo-platform hoặc map-theo-prd_slug, chốt khi target đủ platform/prd_slug) | single-service
|
|
408
408
|
Svc Root : {service_root} — đã nạp conventions + trace_dir từ config service | —
|
|
409
409
|
Status : {FULL | PARTIAL — thiếu: CLAUDE.md / business-dict / core-entities | MINIMAL}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
# SDD Framework — Trace Gate (GitLab CI)
|
|
3
|
+
#
|
|
4
|
+
# COPY nội dung này vào `.gitlab-ci.yml` ở gốc repo. Nó KHÔNG tự chạy từ
|
|
5
|
+
# `.agent/templates/` — mọi thứ trong `.agent/` là bản sinh ra, bị ghi đè mỗi lần
|
|
6
|
+
# `/update-framework`.
|
|
7
|
+
#
|
|
8
|
+
# ⚠️ Repo chưa có `.gitlab-ci.yml`? Thêm file này = mọi push/MR từ nay sẽ kích hoạt
|
|
9
|
+
# pipeline. Đó là mục đích — nhưng hãy biết trước.
|
|
10
|
+
#
|
|
11
|
+
# GIỚI HẠN — đọc trước khi tin:
|
|
12
|
+
# Job này chứng minh "report khớp SỔ, và sổ không có cờ 🔴".
|
|
13
|
+
# Nó KHÔNG chứng minh "sổ khớp CODE" — việc đó cần quét tag trong source, đọc
|
|
14
|
+
# .feature, so version, tức cần `/validate-traces` (một lệnh LLM, không chạy được
|
|
15
|
+
# trong CI thường). Nên nó bắt ca phổ biến "quên chạy lại /validate-traces",
|
|
16
|
+
# nhưng KHÔNG bắt ca "sửa code mà không đụng sổ".
|
|
17
|
+
#
|
|
18
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
19
|
+
# BỐN BẪY KHI DỊCH TỪ GITHUB ACTIONS — file này đã vá cả bốn.
|
|
20
|
+
#
|
|
21
|
+
# Bản GitHub (`trace-gate.yml`) dựa vào ba thứ `ubuntu-latest` cho sẵn mà GitLab
|
|
22
|
+
# KHÔNG có. Hai bẫy đầu ỒN (job đỏ, thấy ngay). Hai bẫy sau IM LẶNG — job XANH
|
|
23
|
+
# trên một phép kiểm chưa bao giờ chạy, tức đúng thứ cổng này tồn tại để chống.
|
|
24
|
+
#
|
|
25
|
+
# 1. ỒN thiếu `tags:` → job stuck "no active runners that can run this job",
|
|
26
|
+
# dù runner Online. Group/shared runner thường có
|
|
27
|
+
# `run_untagged = false`, nên job KHÔNG tag sẽ không
|
|
28
|
+
# bao giờ được nhận. Điền đúng tag của runner bạn.
|
|
29
|
+
# 2. ỒN thiếu Node → `npx: not found`. GitHub có bước
|
|
30
|
+
# `actions/setup-node@v4`; GitLab không có tương
|
|
31
|
+
# đương, và bước đó là chỗ cấp `npx`.
|
|
32
|
+
# 3. IM LẶNG thiếu `GIT_DEPTH: 0` → GitLab clone SHALLOW (mặc định 20). `git diff
|
|
33
|
+
# BASE...HEAD` không có merge-base ⇒ ra RỖNG ⇒ job
|
|
34
|
+
# `require-fresh-audit` in "MR không chạm tag nào"
|
|
35
|
+
# và PASS, trong khi bạn vừa đổi tag. XANH GIẢ.
|
|
36
|
+
# 4. IM LẶNG image không có git → `node:20-alpine` và `node:20-slim` đều KHÔNG có
|
|
37
|
+
# git. Mà lint-trace cần git cho T10 (luật merge) và
|
|
38
|
+
# T11 (bị gitignore) — hai điều kiện `gate-trace` G0
|
|
39
|
+
# NÂNG thành chặn. Không hỏi được git thì (trước T13)
|
|
40
|
+
# G0 in "✅ sổ được bảo vệ — 2/2 điều kiện": một lời
|
|
41
|
+
# KHẲNG ĐỊNH trên bằng chứng chưa bao giờ được thu.
|
|
42
|
+
# Từ v0.7.2 lint phát **T13** và gate FAIL đúng ca đó
|
|
43
|
+
# — nhưng cách sửa vẫn là dùng image CÓ git.
|
|
44
|
+
#
|
|
45
|
+
# `before_script` dưới đây in `node -v` và `git --version` để bẫy 2 và 4 lộ ra ở
|
|
46
|
+
# dòng log đầu tiên, thay vì lộ ra dưới dạng một dấu xanh sai.
|
|
47
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
stages:
|
|
50
|
+
- trace
|
|
51
|
+
|
|
52
|
+
variables:
|
|
53
|
+
# ── BẪY 3 ── BẮT BUỘC. Không có dòng này thì `require-fresh-audit` XANH GIẢ.
|
|
54
|
+
GIT_DEPTH: 0
|
|
55
|
+
|
|
56
|
+
# PIN VERSION, đừng `@latest`. Cổng này quyết định PR nào được merge — một bản
|
|
57
|
+
# release framework đổi hành vi cổng giữa sprint là thứ bạn muốn tự chọn thời điểm.
|
|
58
|
+
SDD_VERSION: "0.7.2"
|
|
59
|
+
|
|
60
|
+
# Sổ/spec nằm trong submodule (umbrella + spec_source)? Bỏ comment dòng dưới.
|
|
61
|
+
# Không có nó, lint thấy 0 sổ → gate FAIL ở G2 ("chưa audit ≠ sạch"). Ồn, không im.
|
|
62
|
+
# GIT_SUBMODULE_STRATEGY: recursive
|
|
63
|
+
|
|
64
|
+
.sdd:
|
|
65
|
+
# ── BẪY 2 + 4 ── `node:20` là bản Debian: có SẴN cả node lẫn git.
|
|
66
|
+
# KHÔNG dùng `node:20-alpine` / `node:20-slim` — chúng thiếu git (xem bẫy 4).
|
|
67
|
+
# Buộc phải dùng alpine? Thêm: `- apk add --no-cache git`
|
|
68
|
+
image: node:20
|
|
69
|
+
|
|
70
|
+
# ── BẪY 1 ── ĐỔI thành đúng tag của runner bạn. Kiểm ở Settings → CI/CD → Runners.
|
|
71
|
+
# Runner Online mà job vẫn stuck ⇒ hoặc lệch tag, hoặc runner không bật
|
|
72
|
+
# "Run untagged jobs", hoặc runner chỉ nhận protected ref (MR pipeline chạy trên
|
|
73
|
+
# `refs/merge-requests/N/head` — KHÔNG phải protected ref, kể cả khi đích là main).
|
|
74
|
+
tags:
|
|
75
|
+
- CHANGE_ME
|
|
76
|
+
|
|
77
|
+
before_script:
|
|
78
|
+
# Hai dòng này tồn tại để bẫy 2 và 4 lộ ra NGAY, chứ không lộ ra dưới dạng dấu xanh sai.
|
|
79
|
+
- node -v
|
|
80
|
+
- git --version
|
|
81
|
+
|
|
82
|
+
# ── 1+2. Cấu trúc sổ + cổng chặn cờ 🔴 ───────────────────────────────────────
|
|
83
|
+
# Sổ trace 24 cột do LLM ghi tay: một dấu tab thiếu dồn mọi ô sang trái, ô `status`
|
|
84
|
+
# nhận một ngày tháng, và không cờ nào bật. lint bắt việc đó (T1/T2/T3), bắt marker
|
|
85
|
+
# conflict git lọt vào sổ (T7), sổ thiếu luật merge (T10), sổ bị gitignore (T11),
|
|
86
|
+
# row vừa DRIFT vừa mang `pass` (T12), và ca không hỏi được git (T13).
|
|
87
|
+
#
|
|
88
|
+
# `--gate-trace` tự chạy lại lint ở tầng G1, nên bước lint riêng ở trên là để có log
|
|
89
|
+
# dễ đọc khi đỏ. Muốn gọn thì bỏ nó và chỉ giữ `--gate-trace`.
|
|
90
|
+
trace-gate:
|
|
91
|
+
extends: .sdd
|
|
92
|
+
stage: trace
|
|
93
|
+
script:
|
|
94
|
+
- npx -y @educa-corp/sdd-framework@${SDD_VERSION} --lint-trace
|
|
95
|
+
- npx -y @educa-corp/sdd-framework@${SDD_VERSION} --gate-trace
|
|
96
|
+
artifacts:
|
|
97
|
+
when: always
|
|
98
|
+
paths:
|
|
99
|
+
- .trace/trace-report.json
|
|
100
|
+
expire_in: 1 week
|
|
101
|
+
rules:
|
|
102
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
103
|
+
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
104
|
+
|
|
105
|
+
# ── 3. Ép audit phải TƯƠI khi thứ report đang KHẲNG ĐỊNH bị đổi ──────────────
|
|
106
|
+
# ĐO BẰNG TAG, KHÔNG BẰNG `src/**`: framework có luật boundary-only tagging, nên
|
|
107
|
+
# "MR có sửa src/ không?" chặn oan cả MR chỉ thêm một field vào DTO — một file không
|
|
108
|
+
# mang lời khẳng định trace nào. Cái gì báo oan thì bị tắt, rồi mất luôn phần cần chặn.
|
|
109
|
+
# Câu hỏi đúng: "MR có chạm dòng nào mang tag mà report đang khẳng định không?"
|
|
110
|
+
# sửa log trong Controller → cho qua
|
|
111
|
+
# thêm field vào DTO → cho qua
|
|
112
|
+
# thêm method + @implements → CHẶN
|
|
113
|
+
# lấp một @trace.stub → CHẶN
|
|
114
|
+
#
|
|
115
|
+
# 7 tag dưới đây khai ở `bin/trace-schema.json` → `gate.audit_invalidating_tags`;
|
|
116
|
+
# `self-check` R10 canh danh sách này khớp schema. Tag đổi tên mà file CI không biết
|
|
117
|
+
# ⇒ grep không khớp gì ⇒ job LUÔN XANH ⇒ cổng mù trong im lặng.
|
|
118
|
+
#
|
|
119
|
+
# GIỚI HẠN ĐÃ BIẾT: đổi tên class (AuthService → AuthenticationService) không chạm
|
|
120
|
+
# dòng tag ⇒ job này bỏ lọt, dù cột `implemented_by` giờ trỏ vào tên không còn. Chấp
|
|
121
|
+
# nhận có chủ ý: ca đó ít gặp và KHÔNG im lặng (lần `/validate-traces` sau báo ngay),
|
|
122
|
+
# còn báo oan thì xảy ra mỗi ngày và hậu quả là CỔNG BỊ TẮT.
|
|
123
|
+
require-fresh-audit:
|
|
124
|
+
extends: .sdd
|
|
125
|
+
stage: trace
|
|
126
|
+
rules:
|
|
127
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
128
|
+
script:
|
|
129
|
+
- |
|
|
130
|
+
# ── BẪY 3, phần thứ hai ── GitHub có `github.base_ref` sẵn trong checkout.
|
|
131
|
+
# GitLab phải FETCH branch đích TRƯỚC — nếu không thì "$BASE" không tồn tại,
|
|
132
|
+
# `git diff` ra rỗng, và job PASS sai. Refspec tường minh để chắc chắn
|
|
133
|
+
# `refs/remotes/origin/<branch>` được cập nhật.
|
|
134
|
+
BR="$CI_MERGE_REQUEST_TARGET_BRANCH_NAME"
|
|
135
|
+
git fetch --no-tags origin "+refs/heads/${BR}:refs/remotes/origin/${BR}"
|
|
136
|
+
BASE="origin/${BR}"
|
|
137
|
+
|
|
138
|
+
# Fail-fast: chứng minh merge-base tồn tại. Không có nó, mọi kết luận bên dưới
|
|
139
|
+
# dựa trên một diff rỗng — và "rỗng" ở đây nghĩa là "không biết", không phải "sạch".
|
|
140
|
+
if ! git merge-base "$BASE" HEAD >/dev/null 2>&1; then
|
|
141
|
+
echo "🔴 Không tìm được merge-base giữa $BASE và HEAD."
|
|
142
|
+
echo " Gần như chắc chắn là clone SHALLOW — kiểm biến GIT_DEPTH: 0 ở đầu file."
|
|
143
|
+
echo " Không có merge-base thì git diff ra rỗng và job này sẽ PASS SAI."
|
|
144
|
+
exit 1
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
TAGS='@trace\.(implements|verifies|seam_port|seam_pending|stub|stub_owner|stub_for)'
|
|
148
|
+
touched=$(git diff -U0 "$BASE"...HEAD | grep -E "^[+-].*${TAGS}" | head -5 || true)
|
|
149
|
+
audit=$(git diff --name-only "$BASE"...HEAD | grep -E 'trace-report\.json$' | head -1 || true)
|
|
150
|
+
|
|
151
|
+
if [ -n "$touched" ] && [ -z "$audit" ]; then
|
|
152
|
+
echo "🔴 MR đổi tag trace nhưng không kèm trace-report.json được sinh lại."
|
|
153
|
+
echo ""
|
|
154
|
+
echo "Những dòng này đã đổi:"
|
|
155
|
+
echo "$touched" | sed 's/^/ /'
|
|
156
|
+
echo ""
|
|
157
|
+
echo "Trace gate chỉ chứng minh 'report khớp SỔ'. Tag vừa đổi mà chưa audit lại"
|
|
158
|
+
echo "thì mọi cờ 🔴 trong report nói về trạng thái TRƯỚC khi bạn sửa."
|
|
159
|
+
echo ""
|
|
160
|
+
echo "Chạy trong Claude Code: /validate-traces"
|
|
161
|
+
echo "Rồi commit: .trace/trace-report.json + *.tsv"
|
|
162
|
+
exit 1
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
if [ -n "$touched" ]; then
|
|
166
|
+
echo "✅ Tag trace có đổi, và audit đã được sinh lại cùng MR."
|
|
167
|
+
else
|
|
168
|
+
echo "✅ MR không chạm tag nào mà report đang khẳng định — audit vẫn còn đúng."
|
|
169
|
+
fi
|
|
@@ -26,7 +26,7 @@ project:
|
|
|
26
26
|
paths:
|
|
27
27
|
# Feature-Package Layout:
|
|
28
28
|
# specs/{domain}/{prd-slug}/{TICKET-ID}-{prd-slug}.md — PRD document
|
|
29
|
-
# specs/{domain}/{prd-slug}/bdd/{platform}/ — BDD .feature ({platform} = web | app | system)
|
|
29
|
+
# specs/{domain}/{prd-slug}/bdd/{platform}/ — BDD .feature ({platform} = web | app | system | webview)
|
|
30
30
|
# specs/{domain}/{prd-slug}/tech-docs/ — Technical design (ONE merged doc per PRD: {TICKET-ID}-tech-design.md)
|
|
31
31
|
# specs/{domain}/{prd-slug}/design-spec/ — Design specs (FE/App only)
|
|
32
32
|
# specs/{domain}/{prd-slug}/changelog/ — PRD changelog overflow (created once history exceeds 5 versions)
|
|
@@ -97,7 +97,7 @@ paths:
|
|
|
97
97
|
# {spec_source}/specs so FE/App read the contract via the spec submodule.
|
|
98
98
|
tech_docs_dir: "specs"
|
|
99
99
|
|
|
100
|
-
# Design Specs (
|
|
100
|
+
# Design Specs (client platforms only — every platform except `system`: web, app, webview).
|
|
101
101
|
# In the feature-package layout, design-specs live at specs/{domain}/{prd-slug}/design-spec/.
|
|
102
102
|
# This variable is no longer needed as a separate path — derived from specs_dir.
|
|
103
103
|
# design_spec_dir: "specs/design-spec" ← removed; use specs_dir instead
|
|
@@ -163,7 +163,7 @@ domains:
|
|
|
163
163
|
# #
|
|
164
164
|
# # FORM B — PER-PLATFORM MAP (one business-domain implemented on several platforms /
|
|
165
165
|
# # submodules — a merged monorepo/workspace). No direct `path`; instead one
|
|
166
|
-
# # sub-key per platform (system | web | app). context-loader routes by the
|
|
166
|
+
# # sub-key per platform (system | web | app | webview). context-loader routes by the
|
|
167
167
|
# # target .feature's @trace.platform → picks {path, module} for that platform.
|
|
168
168
|
# # The PRD keeps a SINGLE business @trace.domain (do NOT invent onboarding-web).
|
|
169
169
|
# {{DOMAIN_2}}:
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
@trace.ucs: {TICKET-ID}-UC1, {TICKET-ID}-UC2{, …}
|
|
40
40
|
@trace.service: {service — từ header BDD @trace.service}
|
|
41
41
|
@trace.module: {module liên quan — vd dotnet, angular}
|
|
42
|
-
@trace.platforms: {system | web | app — tuỳ thư mục BDD nào tồn tại}
|
|
42
|
+
@trace.platforms: {system | web | app | webview | … — tuỳ thư mục BDD nào tồn tại}
|
|
43
43
|
@trace.bdd_versions: {MAP theo từng platform — số nhiều, KHÁC @trace.bdd_version (scalar) của .feature — vd system=1.5, web=1.9, app=1.7; chỉ platform có mặt. Mỗi feature mang bdd_version riêng; đừng gộp về một số.}
|
|
44
44
|
@trace.api_source: {existing | —}
|
|
45
45
|
@trace.revision: 1
|
|
@@ -412,7 +412,7 @@ sequenceDiagram
|
|
|
412
412
|
|
|
413
413
|
| UC | Feature | Platforms | Section phủ | Trạng thái |
|
|
414
414
|
|----|---------|-----------|------------------|--------|
|
|
415
|
-
| {TICKET-ID}-UC1 | {title} | {system, web, app} | §… | ✅ Covered |
|
|
415
|
+
| {TICKET-ID}-UC1 | {title} | {system, web, app, webview…} | §… | ✅ Covered |
|
|
416
416
|
|
|
417
417
|
### Độ phủ Scenario UC1
|
|
418
418
|
|
|
@@ -66,7 +66,7 @@ Spec-driven thành/bại phụ thuộc **~80%** vào việc context được n
|
|
|
66
66
|
bin/self-check.js (fail build) --init cài vào đây
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
**Vì sao slim (G45):** build inline `{{include:}}` vào **từng** file lệnh.
|
|
69
|
+
**Vì sao slim (G45):** build inline `{{include:}}` vào **từng** file lệnh. Đo lúc đó (32 lệnh), kết quả là
|
|
70
70
|
2069 KB mà chỉ 580 KB là nội dung riêng của chúng — **72% là vài step giống hệt nhau, chép 30 lần**.
|
|
71
71
|
`/generate-code` từng nặng 108 KB (≈27k token đọc **trước** khi làm gì), gần một nửa không nói gì về
|
|
72
72
|
việc sinh code. Cái giá thật không phải tiền: trên PRD nhiều UC nó làm tăng rủi ro **cạn context
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
```mermaid
|
|
12
12
|
flowchart TD
|
|
13
13
|
S["0 · Setup<br/>/setup-ai-first"] --> D["1 · Discovery<br/>/define-product"]
|
|
14
|
-
D --> SP["2 · Specification<br/>/generate-prd · /refine-prd · /review-context"]
|
|
14
|
+
D --> SP["2 · Specification<br/>/generate-prd · /extend-prd · /amend-prd<br/>/refine-prd · /review-context"]
|
|
15
15
|
SP --> DS["3 · Design-Spec<br/>(chỉ FE/App)"]
|
|
16
16
|
SP --> B["4 · BDD<br/>/generate-bdd · /review-context"]
|
|
17
17
|
DS --> B
|