@educa-corp/sdd-framework 0.7.2 → 0.7.4
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/self-check.js +71 -0
- package/bin/trace-schema.json +68 -0
- package/core/FRAMEWORK_VERSION +1 -1
- 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/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.4
|