@educa-corp/sdd-framework 0.7.1 → 0.7.3
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/index.js +48 -6
- package/bin/lint-trace.js +64 -3
- package/bin/self-check.js +8 -5
- package/bin/trace-schema.json +15 -1
- package/core/FRAMEWORK_VERSION +1 -1
- package/core/templates/ci/trace-gate.gitlab-ci.yml +169 -0
- package/docs/03-guides/architect.md +3 -2
- package/docs/04-reference/trace-schema.md +2 -0
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -5,6 +5,21 @@ const path = require('path');
|
|
|
5
5
|
|
|
6
6
|
const ROOT = path.join(__dirname, '..');
|
|
7
7
|
|
|
8
|
+
// NGUỒN ASSET ĐỂ CÀI — luôn là `core/`, không bao giờ là dir ở gốc.
|
|
9
|
+
//
|
|
10
|
+
// `core/` là bản dựng sẵn và là thứ DUY NHẤT được ship (package.json → files:
|
|
11
|
+
// bin/ core/ scripts/ docs/). Các dir `modules/`, `hooks/`, `steps/`, `templates/`,
|
|
12
|
+
// `rules/`, `skills/` ở gốc là NGUỒN BUILD — chúng chỉ tồn tại trong dev checkout.
|
|
13
|
+
//
|
|
14
|
+
// Vì sao khai một hằng số cho việc này (hồi quy 0.7.0 / G59): đợt dọn tập publish bỏ
|
|
15
|
+
// `modules/` + `hooks/` khỏi `files`, nhưng BA chỗ trong file này vẫn đọc
|
|
16
|
+
// `path.join(ROOT, 'modules'|'hooks')`. Trong dev checkout chúng vẫn có nên test và
|
|
17
|
+
// `npm run build` đều xanh; trong tarball thì KHÔNG, và `--module` crash cứng bằng
|
|
18
|
+
// ENOENT giữa `--init` — chết TRƯỚC bước `--hooks`, nên một cờ hỏng làm chết cờ kia và
|
|
19
|
+
// project bị cài dở (thiếu .claude/settings.json).
|
|
20
|
+
// Test `bin/ chỉ đọc dir CÓ trong package.json files` canh việc này từ nay.
|
|
21
|
+
const CORE_DIR = path.join(ROOT, 'core');
|
|
22
|
+
|
|
8
23
|
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
|
|
9
24
|
const VERSION = pkg.version;
|
|
10
25
|
|
|
@@ -14,6 +29,12 @@ const VERSION = pkg.version;
|
|
|
14
29
|
// of this file is evaluated, and `const` does not hoist.
|
|
15
30
|
const MANIFEST_NAME = '.install-manifest.json';
|
|
16
31
|
|
|
32
|
+
// Asset được YÊU CẦU tường minh (--module, --hooks, module của service) mà bản cài không
|
|
33
|
+
// có. Gom lại thay vì crash giữa `--init`: crash để lại project cài DỞ — đúng ca hồi quy
|
|
34
|
+
// 0.7.0, nơi ENOENT ở bước --module làm bước --hooks không bao giờ chạy. Nhưng cũng KHÔNG
|
|
35
|
+
// im lặng: exit 1 ở cuối, vì người dùng đã xin thứ họ không nhận được.
|
|
36
|
+
const assetErrors = [];
|
|
37
|
+
|
|
17
38
|
const args = process.argv.slice(2);
|
|
18
39
|
const isMigrateSpecs = args.includes('--migrate-specs');
|
|
19
40
|
const isMigrateBddPf = args.includes('--migrate-bdd-platform');
|
|
@@ -301,11 +322,20 @@ if (isInit) {
|
|
|
301
322
|
console.log(`⚠️ Unknown module: "${moduleName}"`);
|
|
302
323
|
console.log(` Available: ${AVAILABLE_MODULES.join(', ')}`);
|
|
303
324
|
} else {
|
|
304
|
-
const srcModuleDir = path.join(
|
|
325
|
+
const srcModuleDir = path.join(CORE_DIR, 'modules', moduleName);
|
|
305
326
|
const destModuleDir = path.join(agentDir, 'modules', moduleName);
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
327
|
+
// Guard TRƯỚC khi copy. Bản cũ gọi thẳng copyDirRecursive và ENOENT làm chết cả
|
|
328
|
+
// `--init` giữa đường — bước `--hooks` ngay dưới không bao giờ chạy, project bị cài
|
|
329
|
+
// dở. Một cờ hỏng không được phép làm chết cờ khác.
|
|
330
|
+
if (!fs.existsSync(srcModuleDir)) {
|
|
331
|
+
console.log(`❌ Module "${moduleName}" không có trong bản cài: ${path.relative(ROOT, srcModuleDir)}`);
|
|
332
|
+
console.log(` Bản cài thiếu/hỏng — cài lại, hoặc xoá cache npx rồi chạy lại.`);
|
|
333
|
+
assetErrors.push(`module ${moduleName}`);
|
|
334
|
+
} else {
|
|
335
|
+
fs.mkdirSync(destModuleDir, { recursive: true });
|
|
336
|
+
copyDirRecursive(srcModuleDir, destModuleDir);
|
|
337
|
+
console.log(`✅ Module "${moduleName}" installed to: .agent/modules/${moduleName}`);
|
|
338
|
+
}
|
|
309
339
|
}
|
|
310
340
|
}
|
|
311
341
|
|
|
@@ -440,12 +470,18 @@ if (isInit) {
|
|
|
440
470
|
if (!AVAILABLE_MODULES.includes(svc.module)) {
|
|
441
471
|
console.log(` ⚠️ Unknown module "${svc.module}" — skipped`);
|
|
442
472
|
} else {
|
|
443
|
-
const srcMod = path.join(
|
|
473
|
+
const srcMod = path.join(CORE_DIR, 'modules', svc.module);
|
|
444
474
|
const destMod = path.join(svcAgentDir, 'modules', svc.module);
|
|
445
475
|
if (fs.existsSync(srcMod)) {
|
|
446
476
|
fs.mkdirSync(destMod, { recursive: true });
|
|
447
477
|
copyDirRecursive(srcMod, destMod);
|
|
448
478
|
console.log(` ✅ ${svc.name}/.agent/modules/${svc.module}/`);
|
|
479
|
+
} else {
|
|
480
|
+
// Bản cũ bỏ qua ÂM THẦM ở đây: người dùng khai module cho service, không nhận
|
|
481
|
+
// được gì, và không dòng nào báo. Cùng nguồn lỗi với ca --module ở trên nhưng
|
|
482
|
+
// hỏng theo hướng im lặng — tệ hơn.
|
|
483
|
+
console.log(` ❌ ${svc.name}: module "${svc.module}" không có trong bản cài`);
|
|
484
|
+
assetErrors.push(`${svc.name}/module ${svc.module}`);
|
|
449
485
|
}
|
|
450
486
|
}
|
|
451
487
|
}
|
|
@@ -577,6 +613,12 @@ if (isInit) {
|
|
|
577
613
|
console.log('To upgrade later: /update-framework (trong Claude Code)');
|
|
578
614
|
console.log(' (or: npx @educa-corp/sdd-framework@latest --init)');
|
|
579
615
|
console.log('');
|
|
616
|
+
if (assetErrors.length) {
|
|
617
|
+
console.log('');
|
|
618
|
+
console.log(`❌ ${assetErrors.length} asset được yêu cầu nhưng bản cài không có: ${assetErrors.join(' · ')}`);
|
|
619
|
+
console.log(' Phần còn lại của --init đã hoàn tất — nhưng exit 1 vì bạn đã xin thứ không nhận được.');
|
|
620
|
+
process.exit(1);
|
|
621
|
+
}
|
|
580
622
|
process.exit(0);
|
|
581
623
|
}
|
|
582
624
|
|
|
@@ -963,7 +1005,7 @@ function installDataGuardHook() {
|
|
|
963
1005
|
|
|
964
1006
|
// 1. Copy hook script
|
|
965
1007
|
fs.mkdirSync(hooksDir, { recursive: true });
|
|
966
|
-
const srcHook = path.join(
|
|
1008
|
+
const srcHook = path.join(CORE_DIR, 'hooks', 'data-guard.js');
|
|
967
1009
|
const destHook = path.join(hooksDir, 'data-guard.js');
|
|
968
1010
|
fs.copyFileSync(srcHook, destHook);
|
|
969
1011
|
console.log(` ✅ Hook copied to: .claude/hooks/data-guard.js`);
|
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
|
@@ -1259,6 +1259,10 @@
|
|
|
1259
1259
|
"Cùng cách phân vai G39 đã chọn cho pre-push vs CI."
|
|
1260
1260
|
],
|
|
1261
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
|
+
},
|
|
1262
1266
|
{
|
|
1263
1267
|
"rule": "T11",
|
|
1264
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)"
|
|
@@ -1369,7 +1373,17 @@
|
|
|
1369
1373
|
"self-check R10 canh templates/ci/trace-gate.yml có nhắc đủ 7 tag này. Tag đổi tên mà",
|
|
1370
1374
|
"file CI không biết ⇒ grep không khớp gì ⇒ job LUÔN XANH ⇒ cổng mù im lặng."
|
|
1371
1375
|
],
|
|
1372
|
-
"
|
|
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
|
+
],
|
|
1373
1387
|
"tags": [
|
|
1374
1388
|
{
|
|
1375
1389
|
"tag": "@trace.implements",
|
package/core/FRAMEWORK_VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.7.
|
|
1
|
+
0.7.3
|
|
@@ -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
|
|
@@ -98,7 +98,7 @@ sprint thứ ba không ai làm.** Framework có hai lệnh CLI trả exit code
|
|
|
98
98
|
|
|
99
99
|
| Lệnh | Chặn gì | Đặt ở đâu |
|
|
100
100
|
|---|---|---|
|
|
101
|
-
| `--lint-trace` | **Cấu trúc sổ** (
|
|
101
|
+
| `--lint-trace` | **Cấu trúc sổ** (13 rule): header lệch · row sai số ô · enum sai · `sc_id` trùng · marker conflict git · `.jsonl` hỏng. Cộng **T12 — nhất quán GIỮA các ô**: row vừa `status ∈ {DRIFT, ORPHANED}` vừa mang `dev_selftest`/`qc_status = pass`. Cộng hai điều kiện **cấu hình** ở mức ⚠️: thiếu luật merge · sổ bị gitignore | pre-push **và** CI |
|
|
102
102
|
| `--gate-trace` | **Cấu hình** (nâng hai ⚠️ trên thành chặn) + **cờ 🔴**: `ORPHANED` · `TRACE_ORPHAN` · `SEAM_UNWIRED` · `STUB_UNRESOLVED` | CI (cần report tươi) |
|
|
103
103
|
|
|
104
104
|
```bash
|
|
@@ -106,12 +106,13 @@ npx @educa-corp/sdd-framework --lint-trace # exit 1 nếu sổ hỏng
|
|
|
106
106
|
npx @educa-corp/sdd-framework --gate-trace # exit 1 nếu có cờ 🔴
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
-
Mẫu sẵn dùng: `.agent/templates/ci/trace-gate.yml` (GitHub Actions) · `.agent/templates/hooks/pre-push`.
|
|
109
|
+
Mẫu sẵn dùng: `.agent/templates/ci/trace-gate.yml` (GitHub Actions) · `.agent/templates/ci/trace-gate.gitlab-ci.yml` (GitLab CI) · `.agent/templates/hooks/pre-push`.
|
|
110
110
|
**Copy ra khỏi `.agent/`** trước khi dùng — mọi thứ trong đó bị ghi đè mỗi lần `/update-framework`.
|
|
111
111
|
|
|
112
112
|
### Bốn tầng của `--gate-trace`, và vì sao thứ tự đó
|
|
113
113
|
|
|
114
114
|
0. **Sổ được bảo vệ?** — sổ có **nằm trong git**? git có **biết cách gộp** sổ? Kiểm cấu trúc của một quyển sổ sắp mất thì vô nghĩa. Hai điều kiện này `lint-trace` đã phát hiện dưới dạng ⚠️; gate **nâng** chúng thành lỗi chặn — *pre-push nhắc, CI chặn*.
|
|
115
|
+
**Và một điều kiện thứ ba: có kiểm được không?** (`T13`) — thiếu binary `git`, hoặc sổ ngoài mọi repo. Trước v0.7.2, ca này làm G0 in `✅ sổ được bảo vệ — 2/2 điều kiện` trên bằng chứng chưa bao giờ thu, vì G0 lọc warns theo rule id và không thấy gì thì kết luận *"sạch"* thay vì *"không biết"*. Ca thường gặp nhất là **image CI 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.
|
|
115
116
|
1. **Sổ đúng hình dạng?** (gọi `--lint-trace`) — phán trạng thái trên sổ lệch cột là phán trên dữ liệu rác.
|
|
116
117
|
2. **Report còn tươi VÀ phủ toàn bộ?** — đối chiếu `trace-report.json` với chính sổ TSV nó khai
|
|
117
118
|
là đang mô tả. Không có tầng này thì cổng là **sân khấu**: chạy `/validate-traces` một lần,
|
|
@@ -193,6 +193,8 @@ Shared code dò qua **import chain** từ boundary → tránh tag explosion.
|
|
|
193
193
|
>
|
|
194
194
|
> ⓘ = **không phải lỗi.** Hai cờ `*_STALE_REF` tồn tại vì version PRD/tech-doc là **MỘT số cho cả tài liệu nhiều UC** — thêm một UC làm mọi UC cũ lệch số dù không đổi một chữ. Không lọc thì cả loạt UC ăn cờ đỏ oan, và làm theo hướng dẫn cũng không tắt được (`/generate-code` skip row đang `OK`). Sạch bằng `--realign-*`: chỉ sửa dòng `@trace.*`, **không đụng logic**, và **từ chối chạy** nếu UC đó đang thật sự `DRIFT`/`ORPHANED`.
|
|
195
195
|
>
|
|
196
|
+
> **Không kiểm được ≠ sạch.** `lint-trace` **T13** phát khi không hỏi được `git` (image CI thiếu git, hoặc sổ ngoài repo) — và `gate-trace` G0 **fail**, thay vì khẳng định `sổ được bảo vệ — 2/2 điều kiện` trên bằng chứng chưa bao giờ thu. Cùng nguyên tắc `spec_edit_detection.degraded`: *không bịa cờ, và không im lặng*.
|
|
197
|
+
|
|
196
198
|
> **Mọi cờ đều PHẢI có counter `{flag}_count`** trong Step 7 + `summary` của `trace-report.json` — `bin/self-check.js` R7 ép, không có ngoại lệ. Thiếu counter = cờ vô hình với dashboard.
|
|
197
199
|
|
|
198
200
|
---
|