@educa-corp/sdd-framework 0.7.3 → 0.7.5
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/gate-trace.js +13 -1
- package/bin/index.js +18 -1
- package/bin/lint-trace.js +14 -1
- package/bin/self-check.js +71 -0
- package/bin/trace-schema.json +68 -0
- package/core/FRAMEWORK_VERSION +1 -1
- package/core/templates/ci/trace-gate.gitlab-ci.yml +7 -1
- package/package.json +1 -1
package/bin/gate-trace.js
CHANGED
|
@@ -63,6 +63,18 @@ const NO_RECONCILE = argv.includes('--no-reconcile');
|
|
|
63
63
|
const TRACE_DIRS = flag('--trace', '.trace').split(',').map(s => s.trim()).filter(Boolean);
|
|
64
64
|
const REPORT_ARG = flag('--report', null);
|
|
65
65
|
|
|
66
|
+
// Version của framework — IN RA MỌI LẦN CHẠY.
|
|
67
|
+
//
|
|
68
|
+
// Vì sao (báo cáo từ project thật): người dùng chạy `npx …@${SDD_VERSION} --lint-trace`
|
|
69
|
+
// trong CI và KHÔNG có cách nào biết bản nào thật sự chạy. `npx` cache theo version, biến
|
|
70
|
+
// CI có thể rỗng, và `.agent/FRAMEWORK_VERSION` chỉ nói bản ĐÃ CÀI chứ không nói bản đang
|
|
71
|
+
// KIỂM. Nên câu "lỗi này sửa chưa?" không trả lời được từ log — đúng lúc cần nhất.
|
|
72
|
+
// Một cổng quyết định PR nào được merge thì phải nói nó là ai.
|
|
73
|
+
const FW_VERSION = (() => {
|
|
74
|
+
try { return require(path.join(__dirname, '..', 'package.json')).version; }
|
|
75
|
+
catch { return '?'; }
|
|
76
|
+
})();
|
|
77
|
+
|
|
66
78
|
const fails = [];
|
|
67
79
|
const warns = [];
|
|
68
80
|
const oks = [];
|
|
@@ -434,7 +446,7 @@ if (AS_JSON) {
|
|
|
434
446
|
}
|
|
435
447
|
|
|
436
448
|
console.log('');
|
|
437
|
-
console.log(
|
|
449
|
+
console.log(`Trace gate v${FW_VERSION} (bin/trace-schema.json → gate) ...`);
|
|
438
450
|
console.log('');
|
|
439
451
|
|
|
440
452
|
for (const o of oks) console.log(` ✅ ${o}`);
|
package/bin/index.js
CHANGED
|
@@ -45,6 +45,10 @@ const isInit = args.includes('--init');
|
|
|
45
45
|
const isProject = args.includes('--project');
|
|
46
46
|
const installHooks = args.includes('--hooks');
|
|
47
47
|
const showHelp = args.includes('--help') || args.includes('-h');
|
|
48
|
+
// `--version` / `-v` — trả lời "bản nào đang chạy?" bằng MỘT lệnh, không cần đoán.
|
|
49
|
+
// Cần thiết vì `npx` cache theo version và biến CI có thể rỗng: `npx pkg@${VAR}` với VAR
|
|
50
|
+
// rỗng thành `pkg@` ⇒ npm resolve về latest, và log không nói gì về việc đó.
|
|
51
|
+
const showVersion = args.includes('--version') || args.includes('-v');
|
|
48
52
|
|
|
49
53
|
// --module <name> flag
|
|
50
54
|
const moduleIdx = args.indexOf('--module');
|
|
@@ -97,8 +101,21 @@ const MODULE_STACK = {
|
|
|
97
101
|
'phaser-game': { language: 'TypeScript', framework: 'Phaser 3' },
|
|
98
102
|
};
|
|
99
103
|
|
|
104
|
+
if (showVersion) {
|
|
105
|
+
// In cả hai: version package VÀ version của core/ dựng sẵn. Lệch nhau nghĩa là bản cài
|
|
106
|
+
// hỏng (index.js §corePrebuilt sẽ xử) — thấy được ở đây thì đỡ một vòng chẩn đoán.
|
|
107
|
+
let coreV = '(không có core/)';
|
|
108
|
+
try { coreV = fs.readFileSync(path.join(ROOT, 'core', 'FRAMEWORK_VERSION'), 'utf8').trim(); } catch {}
|
|
109
|
+
console.log(`@educa-corp/sdd-framework ${VERSION}`);
|
|
110
|
+
console.log(` core/FRAMEWORK_VERSION : ${coreV}${coreV === VERSION ? '' : ' ⚠️ LỆCH — bản cài hỏng'}`);
|
|
111
|
+
process.exit(0);
|
|
112
|
+
}
|
|
113
|
+
|
|
100
114
|
if (showHelp) {
|
|
101
|
-
console.log(
|
|
115
|
+
console.log(`Usage: npx @educa-corp/sdd-framework [options] (v${VERSION})`);
|
|
116
|
+
console.log('');
|
|
117
|
+
console.log('');
|
|
118
|
+
console.log(' --version, -v In version đang chạy (package + core/). Dùng để chắc `npx` không lấy bản cache.');
|
|
102
119
|
console.log('');
|
|
103
120
|
console.log('Install modes:');
|
|
104
121
|
console.log(' --init Install framework to .agent/ + create shortcuts in .claude/commands/');
|
package/bin/lint-trace.js
CHANGED
|
@@ -53,6 +53,18 @@ const flag = (name, def) => {
|
|
|
53
53
|
const i = argv.indexOf(name);
|
|
54
54
|
return i !== -1 && argv[i + 1] && !argv[i + 1].startsWith('--') ? argv[i + 1] : def;
|
|
55
55
|
};
|
|
56
|
+
// Version của framework — IN RA MỌI LẦN CHẠY.
|
|
57
|
+
//
|
|
58
|
+
// Vì sao (báo cáo từ project thật): người dùng chạy `npx …@${SDD_VERSION} --lint-trace`
|
|
59
|
+
// trong CI và KHÔNG có cách nào biết bản nào thật sự chạy. `npx` cache theo version, biến
|
|
60
|
+
// CI có thể rỗng, và `.agent/FRAMEWORK_VERSION` chỉ nói bản ĐÃ CÀI chứ không nói bản đang
|
|
61
|
+
// KIỂM. Nên câu "lỗi này sửa chưa?" không trả lời được từ log — đúng lúc cần nhất.
|
|
62
|
+
// Một cổng quyết định PR nào được merge thì phải nói nó là ai.
|
|
63
|
+
const FW_VERSION = (() => {
|
|
64
|
+
try { return require(path.join(__dirname, '..', 'package.json')).version; }
|
|
65
|
+
catch { return '?'; }
|
|
66
|
+
})();
|
|
67
|
+
|
|
56
68
|
const WARN_ONLY = argv.includes('--warn-only');
|
|
57
69
|
const AS_JSON = argv.includes('--json');
|
|
58
70
|
const TRACE_DIRS = flag('--trace', '.trace').split(',').map(s => s.trim()).filter(Boolean);
|
|
@@ -657,13 +669,14 @@ for (const dir of TRACE_DIRS) {
|
|
|
657
669
|
if (AS_JSON) {
|
|
658
670
|
console.log(JSON.stringify({
|
|
659
671
|
ok: errors.length === 0,
|
|
672
|
+
framework_version: FW_VERSION,
|
|
660
673
|
scanned, trace_dirs: foundDirs, git_state: _gitState, errors, warns, infos,
|
|
661
674
|
}, null, 2));
|
|
662
675
|
process.exit(errors.length && !WARN_ONLY ? 1 : 0);
|
|
663
676
|
}
|
|
664
677
|
|
|
665
678
|
console.log('');
|
|
666
|
-
console.log(
|
|
679
|
+
console.log(`Lint trace v${FW_VERSION} (bin/trace-schema.json) ...`);
|
|
667
680
|
console.log('');
|
|
668
681
|
|
|
669
682
|
if (!foundDirs.length) {
|
package/bin/self-check.js
CHANGED
|
@@ -1111,6 +1111,77 @@ for (const [vocab, def] of Object.entries(schema.vocabularies)) {
|
|
|
1111
1111
|
}
|
|
1112
1112
|
}
|
|
1113
1113
|
|
|
1114
|
+
// ── R15 — field nguy hiểm phải được DÙNG, không chỉ được NHẮC TÊN ─────────────
|
|
1115
|
+
//
|
|
1116
|
+
// R3 hỏi "file có nhắc tên field không". Đó là câu SAI cho những field mà trả lời sai
|
|
1117
|
+
// gây hại im lặng. Ca thật: schema khai cột `status` có read_by gồm dev-run-test và
|
|
1118
|
+
// qc-run-test; R3 xác nhận ✅ vì cả hai file CÓ nhắc `status` — trong đúng câu khai rằng
|
|
1119
|
+
// chúng "trực giao" với nó, tức CỐ Ý KHÔNG dùng. Số lần đọc thật: 0. Đó là G55, và R3
|
|
1120
|
+
// xác nhận contract bằng đúng câu văn đang phủ nhận contract.
|
|
1121
|
+
//
|
|
1122
|
+
// R15 KHÔNG thay R3 và KHÔNG áp cho mọi field — nó siết đúng danh sách khai ở
|
|
1123
|
+
// `strict_use_check.fields`. Lý do không siết toàn cục nằm ở `$comment` của khối đó;
|
|
1124
|
+
// tóm gọn: (a) 42 tag × 33 lệnh ⇒ báo oan ⇒ checker bị tắt ⇒ mất cả phần R3 bắt thật;
|
|
1125
|
+
// (b) file lệnh là VĂN XUÔI và văn xuôi CHÍNH LÀ chương trình, nên không tồn tại dấu
|
|
1126
|
+
// hiệu cú pháp tách "câu dùng giá trị" khỏi "câu nói về giá trị".
|
|
1127
|
+
//
|
|
1128
|
+
// Anchor = DÒNG BẢNG MARKDOWN, ngoài blockquote. Không phải heuristic tuỳ ý: framework
|
|
1129
|
+
// này viết mọi luật rẽ nhánh bằng bảng. Đã kiểm chứng trên bản dev-run-test TRƯỚC G55 —
|
|
1130
|
+
// R3 PASS, R15 FAIL — và trên bản hiện tại, cả 5 field trong danh sách đều PASS.
|
|
1131
|
+
{
|
|
1132
|
+
const SUC = schema.strict_use_check;
|
|
1133
|
+
if (SUC) {
|
|
1134
|
+
if (SUC.anchor !== 'markdown_table_row') {
|
|
1135
|
+
err('R15', `\`strict_use_check.anchor\` = "${SUC.anchor}" — R15 chỉ implement \`markdown_table_row\``,
|
|
1136
|
+
'Đổi anchor trong schema mà không dạy R15 nghĩa là rule này im lặng theo một luật khác.');
|
|
1137
|
+
}
|
|
1138
|
+
if (!(SUC.fields || []).length) {
|
|
1139
|
+
warn('R15', '`strict_use_check.fields` rỗng — rule không siết field nào',
|
|
1140
|
+
'Khối tồn tại mà không khai field nào là một cái cổng không canh gì. Khai field khi\n'
|
|
1141
|
+
+ ' phát hiện nó nguy hiểm, đừng để danh sách trống.');
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// Dòng bảng markdown, NGOÀI blockquote. Blockquote là chỗ của giải thích và trích dẫn
|
|
1145
|
+
// lịch sử — hai thứ ĐƯỢC PHÉP nhắc tên field mà không dùng nó.
|
|
1146
|
+
const isTableRow = (line) => {
|
|
1147
|
+
const t = line.trim();
|
|
1148
|
+
if (t.startsWith('>')) return false;
|
|
1149
|
+
return (t.match(/\|/g) || []).length >= 2;
|
|
1150
|
+
};
|
|
1151
|
+
|
|
1152
|
+
for (const entry of SUC.fields || []) {
|
|
1153
|
+
if (!entry.why) {
|
|
1154
|
+
err('R15', `\`strict_use_check.fields\` mục \`${entry.field}\` thiếu \`why\``,
|
|
1155
|
+
'Một field được siết mà không nói VÌ SAO sẽ bị người sau coi là tuỳ ý và gỡ khỏi danh sách.');
|
|
1156
|
+
}
|
|
1157
|
+
for (const actor of entry.actors || []) {
|
|
1158
|
+
const rel = schema.actors[actor] || `commands/${actor}.tmpl`;
|
|
1159
|
+
const abs = path.join(ROOT, rel);
|
|
1160
|
+
if (!fs.existsSync(abs)) {
|
|
1161
|
+
err('R15', `\`strict_use_check\` khai actor \`${actor}\` — ${rel} không tồn tại`);
|
|
1162
|
+
continue;
|
|
1163
|
+
}
|
|
1164
|
+
const text = fs.readFileSync(abs, 'utf8');
|
|
1165
|
+
// Điều kiện tiên quyết: R3 phải pass trước. Nếu file không nhắc field CHÚT NÀO thì
|
|
1166
|
+
// đó là việc của R3 — đừng báo hai lần cùng một chuyện bằng hai giọng khác nhau.
|
|
1167
|
+
if (!mentions(text, entry.field)) continue;
|
|
1168
|
+
|
|
1169
|
+
const inDecision = text.split('\n').filter(isTableRow)
|
|
1170
|
+
.some(l => mentions(l, entry.field));
|
|
1171
|
+
if (!inDecision) {
|
|
1172
|
+
err('R15', `${rel} nhắc \`${entry.field}\` nhưng KHÔNG ở một dòng bảng nào — nhắc tên, không phải dùng`,
|
|
1173
|
+
`Vì sao siết field này: ${entry.why}\n`
|
|
1174
|
+
+ ` R3 đã pass (file CÓ nhắc tên). Nhưng framework viết mọi luật rẽ nhánh bằng\n`
|
|
1175
|
+
+ ` BẢNG, nên một field chỉ xuất hiện trong câu văn là dấu hiệu nó đang được\n`
|
|
1176
|
+
+ ` NÓI TỚI, không được DÙNG — đúng hình dạng G55, nơi câu "trực giao với\n`
|
|
1177
|
+
+ ` \`status\`" thoả mãn R3 trong khi chính nó khai là cố ý bỏ qua.\n`
|
|
1178
|
+
+ ` Sửa: đưa \`${entry.field}\` vào bảng quyết định của lệnh (điều kiện → hành động).`);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1114
1185
|
// ── report ────────────────────────────────────────────────────────────────────
|
|
1115
1186
|
const tagCount = schema.tags.length;
|
|
1116
1187
|
const colCount = schema.tsv_columns.length;
|
package/bin/trace-schema.json
CHANGED
|
@@ -1771,6 +1771,74 @@
|
|
|
1771
1771
|
}
|
|
1772
1772
|
]
|
|
1773
1773
|
},
|
|
1774
|
+
"strict_use_check": {
|
|
1775
|
+
"$comment": [
|
|
1776
|
+
"R3 CANH 'CÓ NHẮC TÊN', KHÔNG CANH 'CÓ DÙNG'. Khối này siết đúng những field mà trả lời",
|
|
1777
|
+
"sai gây hại IM LẶNG — và chỉ những field đó.",
|
|
1778
|
+
"",
|
|
1779
|
+
"R3 kiểm lời khai producer/consumer bằng cách tìm tên field trong file actor. Thấy → ✅.",
|
|
1780
|
+
"Nhưng một file có thể chứa tên field vì nhiều lý do KHÔNG PHẢI 'đang dùng nó':",
|
|
1781
|
+
" 1. câu tuyên bố CỐ Ý BỎ QUA — G55: /dev-run-test viết 'dev_selftest trực giao với",
|
|
1782
|
+
" `status`', tức chính câu đó khai rằng nó không dùng. R3 thấy chữ, đóng dấu ✅, và số",
|
|
1783
|
+
" lần đọc thật là 0. R3 xác nhận một contract bằng đúng câu văn đang phủ nhận contract.",
|
|
1784
|
+
" 2. chú thích giải thích luật — R9(h): comment viết `scope.kind !== 'all'` trong khi code",
|
|
1785
|
+
" đã thành `false`.",
|
|
1786
|
+
" 3. trích dẫn lịch sử — 'Bản cũ ghi cứng X'. Ca này HỢP LỆ và phải được phép tồn tại:",
|
|
1787
|
+
" framework cố ý giữ luật cũ để người sau biết luật mới chống cái gì.",
|
|
1788
|
+
" 4. substring — `DRIFTED` chứa `DRIFT`. `mentions()` đã chặn được ca này (xem doc-comment",
|
|
1789
|
+
" của nó); ba ca trên thì không.",
|
|
1790
|
+
"",
|
|
1791
|
+
"VÌ SAO KHÔNG SIẾT R3 TOÀN CỤC — hai lý do, cái thứ hai là gốc:",
|
|
1792
|
+
" (a) R3 áp cho 42 tag + 24 cột × 33 lệnh. Siết toàn cục đẻ ra một loạt báo oan, và cái gì",
|
|
1793
|
+
" báo oan thì bị TẮT — rồi mất luôn phần R3 đang bắt thật (G5, G12, G13, G31, G32).",
|
|
1794
|
+
" (b) FILE LỆNH LÀ VĂN XUÔI, VÀ VĂN XUÔI CHÍNH LÀ CHƯƠNG TRÌNH. Với .js thì tách được code",
|
|
1795
|
+
" khỏi comment (`codeOf()`). Với .tmpl thì KHÔNG TỒN TẠI dấu hiệu cú pháp nào tách",
|
|
1796
|
+
" 'câu chỉ dẫn dùng giá trị' khỏi 'câu nói về giá trị' — cả hai đều là câu tiếng Việt.",
|
|
1797
|
+
" Đây là tính chất của thiết kế framework, không sửa được bằng một rule.",
|
|
1798
|
+
"",
|
|
1799
|
+
"NÊN: opt-in, khoanh vùng. Cùng khuôn `positive_assertion_guards` và `checkpoint_levels` —",
|
|
1800
|
+
"khai tường minh từng field nguy hiểm rồi để máy canh, thay vì một cú siết toàn cục.",
|
|
1801
|
+
"",
|
|
1802
|
+
"ANCHOR = DÒNG BẢNG MARKDOWN. Đây không phải heuristic tuỳ ý: framework này viết MỌI luật",
|
|
1803
|
+
"rẽ nhánh bằng bảng — validate-traces Step 3 (`| Rule | Status | Điều kiện |`), gate.md Bước",
|
|
1804
|
+
"3a (`| Mức | Lệnh nào |`), generate-code §Read Trace State (`| Status | Hành động |`),",
|
|
1805
|
+
"dev-run-test §Guard (`| status của row | ghi gì |`). Một field được DÙNG để quyết định thì",
|
|
1806
|
+
"nó xuất hiện trong bảng. Một field chỉ được NHẮC TỚI thì nằm trong câu văn.",
|
|
1807
|
+
"Blockquote (`>`) bị loại: đó là chỗ của giải thích và trích dẫn lịch sử (ca 3 ở trên).",
|
|
1808
|
+
"",
|
|
1809
|
+
"KIỂM CHỨNG ANCHOR trên bản dev-run-test TRƯỚC G55: R3 PASS (lý do G55 sống được),",
|
|
1810
|
+
"R15 FAIL. Và trên bản hiện tại: cả 8 field trong danh sách đều PASS — không báo oan."
|
|
1811
|
+
],
|
|
1812
|
+
"anchor": "markdown_table_row",
|
|
1813
|
+
"$anchor_note": "Dòng có ≥2 ký tự `|` sau khi trim, và KHÔNG bắt đầu bằng `>`. Khớp tên field theo ranh giới token (cùng hàm `mentions()` của R3) — nên `qc_status` KHÔNG thoả cho field `status`.",
|
|
1814
|
+
"fields": [
|
|
1815
|
+
{
|
|
1816
|
+
"field": "status",
|
|
1817
|
+
"actors": ["dev-run-test", "qc-run-test"],
|
|
1818
|
+
"why": "G55 — đây là ca đã xảy ra thật. Không rẽ nhánh theo `status` thì hai lệnh ghi `pass` trên row `DRIFT`, tức dựng lại đúng tín hiệu `/generate-bdd` vừa hạ. Cờ NÓI DỐI, không phải cờ im lặng."
|
|
1819
|
+
},
|
|
1820
|
+
{
|
|
1821
|
+
"field": "spec_ver",
|
|
1822
|
+
"actors": ["generate-code"],
|
|
1823
|
+
"why": "Một nửa của phép so sinh ra DRIFT. Không đọc thì `/generate-code` skip mọi row lỗi thời và không gì báo — spec đổi mà code không bao giờ theo."
|
|
1824
|
+
},
|
|
1825
|
+
{
|
|
1826
|
+
"field": "gen_ver",
|
|
1827
|
+
"actors": ["generate-code"],
|
|
1828
|
+
"why": "Nửa còn lại. Thiếu một nửa thì phép so vô nghĩa, và nó vô nghĩa trong IM LẶNG."
|
|
1829
|
+
},
|
|
1830
|
+
{
|
|
1831
|
+
"field": "implemented_by",
|
|
1832
|
+
"actors": ["generate-code", "generate-bdd"],
|
|
1833
|
+
"why": "Phân biệt UNTRACKED (chưa có code) với ORPHANED (code còn, spec mất). Đọc sai thì `/generate-bdd` xoá row của một scenario ĐÃ có code ⇒ method đó thành vô hình: không cờ nào, không report nào, mà vẫn được caller gọi."
|
|
1834
|
+
},
|
|
1835
|
+
{
|
|
1836
|
+
"field": "test_count",
|
|
1837
|
+
"actors": ["validate-traces"],
|
|
1838
|
+
"why": "Quyết GAP vs OK. Không đọc thì mọi row có code đều thành OK ⇒ coverage báo xanh trên scenario chưa có một test nào."
|
|
1839
|
+
}
|
|
1840
|
+
]
|
|
1841
|
+
},
|
|
1774
1842
|
"path_keys": {
|
|
1775
1843
|
"$comment": "Key phải có trong templates/project-context.yaml VÀ được dùng bởi ít nhất một actor. Key khai mà không ai dùng = key chết (GAPS.md G17).",
|
|
1776
1844
|
"config_file": "templates/project-context.yaml",
|
package/core/FRAMEWORK_VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.7.
|
|
1
|
+
0.7.5
|
|
@@ -75,9 +75,15 @@ variables:
|
|
|
75
75
|
- CHANGE_ME
|
|
76
76
|
|
|
77
77
|
before_script:
|
|
78
|
-
#
|
|
78
|
+
# Ba dòng này tồn tại để bẫy 2, 4 và "npx lấy bản cache" lộ ra NGAY ở đầu log, chứ không
|
|
79
|
+
# lộ ra dưới dạng một dấu xanh sai.
|
|
79
80
|
- node -v
|
|
80
81
|
- git --version
|
|
82
|
+
# CHỨNG MINH version đã pin. `npx` cache theo version, và một biến CI rỗng làm
|
|
83
|
+
# `pkg@${SDD_VERSION}` thành `pkg@` ⇒ npm resolve về latest mà log không nói gì.
|
|
84
|
+
# Dòng này in ra version THẬT sẽ chạy — nếu nó khác SDD_VERSION thì bạn thấy ngay.
|
|
85
|
+
- echo "SDD_VERSION=${SDD_VERSION}"
|
|
86
|
+
- npx -y @educa-corp/sdd-framework@${SDD_VERSION} --version
|
|
81
87
|
|
|
82
88
|
# ── 1+2. Cấu trúc sổ + cổng chặn cờ 🔴 ───────────────────────────────────────
|
|
83
89
|
# Sổ trace 24 cột do LLM ghi tay: một dấu tab thiếu dồn mọi ô sang trái, ô `status`
|