@educa-corp/sdd-framework 0.9.1 → 0.9.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.
Files changed (69) hide show
  1. package/bin/qc-base-map.json +595 -0
  2. package/bin/self-check.js +146 -0
  3. package/core/FRAMEWORK_VERSION +1 -1
  4. package/core/commands/propose-scenario.md +1 -1
  5. package/core/commands/qc-analyze.md +398 -37
  6. package/core/commands/qc-design-test.md +141 -2
  7. package/core/commands/qc-plan.md +159 -10
  8. package/core/commands/qc-review.md +134 -1
  9. package/core/commands/qc-run-test.md +135 -2
  10. package/core/commands/refine-prd.md +47 -20
  11. package/core/commands/report-bug.md +1 -1
  12. package/core/commands/review-context.md +27 -1
  13. package/core/modules/qc-playwright/stack-profile.yaml +3 -3
  14. package/core/skills/qc/qa-analyst/DOC_GAP.template.md +147 -0
  15. package/core/skills/qc/qa-analyst/acceptance-criteria.md +5 -3
  16. package/core/skills/qc/qa-analyst/business-rules.md +39 -5
  17. package/core/skills/qc/qa-analyst/data-flow.md +6 -4
  18. package/core/skills/qc/qa-analyst/exhaustive-gap-scanner.md +174 -0
  19. package/core/skills/qc/qa-analyst/spec-breakdown.md +10 -8
  20. package/core/skills/qc/qa-analyst/spec-issue-reporter.md +112 -0
  21. package/core/skills/qc/qa-designer/e2e/journey.md +3 -3
  22. package/core/skills/qc/qa-designer/exploratory/charter.md +1 -1
  23. package/core/skills/qc/qa-designer/exploratory/explore-to-functional.md +2 -2
  24. package/core/skills/qc/qa-designer/functional/api.md +3 -3
  25. package/core/skills/qc/qa-designer/functional/gui-feature.md +3 -3
  26. package/core/skills/qc/qa-designer/functional/gui-screen.md +3 -3
  27. package/core/skills/qc/qa-designer/integration/api.md +3 -3
  28. package/core/skills/qc/qa-designer/integration/db.md +3 -3
  29. package/core/skills/qc/qa-designer/integration/gui.md +3 -3
  30. package/core/skills/qc/qa-designer/integration/kafka.md +3 -3
  31. package/core/skills/qc/qa-designer/non-functional.md +3 -3
  32. package/core/skills/qc/qa-planner/risk-model.md +106 -0
  33. package/core/skills/qc/qa-planner/test-plan.md +35 -21
  34. package/core/skills/qc/qa-reviewer/script/e2e.md +1 -1
  35. package/core/skills/qc/qa-reviewer/script/exploratory.md +1 -1
  36. package/core/skills/qc/qa-reviewer/script/functional.md +1 -1
  37. package/core/skills/qc/qa-reviewer/script/integration.md +1 -1
  38. package/core/skills/qc/qa-reviewer/script/non-functional.md +1 -1
  39. package/core/skills/qc/qa-reviewer/test-case/e2e.md +1 -1
  40. package/core/skills/qc/qa-reviewer/test-case/exploratory.md +1 -1
  41. package/core/skills/qc/qa-reviewer/test-case/functional.md +1 -1
  42. package/core/skills/qc/qa-reviewer/test-case/integration.md +2 -2
  43. package/core/skills/qc/qa-reviewer/test-case/non-functional.md +1 -1
  44. package/core/skills/qc/qa-runner/e2e.md +1 -1
  45. package/core/skills/qc/qa-runner/exploratory/session.md +2 -2
  46. package/core/skills/qc/qa-runner/functional/api.md +1 -1
  47. package/core/skills/qc/qa-runner/functional/gui-feature.md +1 -1
  48. package/core/skills/qc/qa-runner/functional/gui-screen.md +1 -1
  49. package/core/skills/qc/qa-runner/integration.md +1 -1
  50. package/core/skills/qc/qa-runner/non-functional.md +1 -1
  51. package/core/skills/qc/qa-runner/report/report.md +1 -1
  52. package/core/steps/context-loader.md +1 -1
  53. package/core/steps/gap-verify.md +231 -0
  54. package/core/steps/qc-scope.md +119 -0
  55. package/core/steps/review-fanout.md +27 -1
  56. package/core/templates/project-context.yaml +5 -3
  57. package/docs/02-concepts/pipeline-steps/08-qc-automation.md +2 -2
  58. package/docs/04-reference/commands.md +1 -1
  59. package/docs/04-reference/configuration.md +146 -146
  60. package/docs/explain/03-refine-prd.md +8 -6
  61. package/docs/explain/15-qc-analyze.md +10 -7
  62. package/docs/explain/16-qc-plan.md +3 -3
  63. package/docs/explain/17-qc-design-test.md +1 -1
  64. package/docs/plans/qc-implementation-log.md +1587 -0
  65. package/docs/plans/qc-merge-plan.md +502 -0
  66. package/docs/plans/qc-sync-command.md +359 -0
  67. package/package.json +1 -1
  68. package/scripts/migrate-qc-docs.js +261 -0
  69. package/core/skills/qc/qa-analyst/DOC_GAPS.template.md +0 -63
package/bin/self-check.js CHANGED
@@ -61,6 +61,7 @@
61
61
 
62
62
  const fs = require('fs');
63
63
  const path = require('path');
64
+ const crypto = require('crypto'); // R16(b) — băm file snapshot để đối chiếu base_sha
64
65
 
65
66
  const ROOT = path.join(__dirname, '..');
66
67
  const WARN_ONLY = process.argv.includes('--warn-only');
@@ -1182,6 +1183,151 @@ for (const [vocab, def] of Object.entries(schema.vocabularies)) {
1182
1183
  }
1183
1184
  }
1184
1185
 
1186
+ // ── R16 — snapshot upstream và framework không được trôi khỏi nhau trong im lặng ─
1187
+ //
1188
+ // Hai sự cố thật đã xảy ra trong lúc port bộ QC, và KHÔNG tầng nào bắt được:
1189
+ //
1190
+ // (1) 35 file skill QC bị xoá khỏi skills/. self-check ✅ · build ✅ 41/41 · .agent vẫn
1191
+ // chạy được (mirror KHÔNG prune nên bản cũ còn nằm đó) — nhưng core/ thì rỗng, và
1192
+ // bin/index.js prune sẽ CHỦ ĐỘNG xoá chúng ở project người dùng khi nâng cấp (đúng
1193
+ // luật: có trong manifest + không còn ship + hash khớp). Ba tầng che nhau; hỏng chỉ
1194
+ // lộ ở phía người dùng. → R16(d).
1195
+ //
1196
+ // (2) Một `upstream_sha` được GÕ BỪA vào frontmatter thay vì tính. Tự phát hiện, nhưng
1197
+ // nếu lọt thì nó là BASE sai cho mọi lần merge ba chiều về sau — tức mọi lần đồng bộ
1198
+ // sau đều đoán. Đây đúng loại việc con người không làm đáng tin bằng sự cẩn thận.
1199
+ // → R16(b).
1200
+ //
1201
+ // (a) là cái quan trọng nhất về lâu dài: file upstream KHÔNG có entry trong bản đồ =
1202
+ // bị bỏ quên trong im lặng. `state: skipped` (có lý do) là câu trả lời hợp lệ; VẮNG MẶT
1203
+ // thì không. Cùng khuôn với `checkpoint_levels` và `strict_use_check`: khai tường minh
1204
+ // từng cái, rồi để máy canh — thay vì tin rằng ai đó sẽ nhớ.
1205
+ //
1206
+ // R16 tự bỏ qua nếu chưa có bin/qc-base-map.json (repo chưa port bộ QC) — không ép
1207
+ // mọi consumer phải có bản đồ này.
1208
+ {
1209
+ const MAP_PATH = path.join(ROOT, 'bin/qc-base-map.json');
1210
+ const SNAP_DIR = path.join(ROOT, 'upstream/qc-base');
1211
+ const hasMap = fs.existsSync(MAP_PATH);
1212
+ const hasSnap = fs.existsSync(SNAP_DIR);
1213
+
1214
+ // ── (d) lệnh qc-* trỏ vào skill nào thì skill đó phải tồn tại ────────────────
1215
+ // Chạy ĐỘC LẬP với bản đồ: đây là lớp lỗi (1) ở trên, và nó không cần snapshot.
1216
+ const SKILLS_QC = path.join(ROOT, 'skills/qc');
1217
+ if (fs.existsSync(SKILLS_QC)) {
1218
+ const cmdDir = path.join(ROOT, 'commands');
1219
+ for (const f of fs.readdirSync(cmdDir).filter(n => /^qc-.*\.tmpl$/.test(n))) {
1220
+ const txt = fs.readFileSync(path.join(cmdDir, f), 'utf8');
1221
+ // `{paths.qc_skills_dir}/<đường dẫn>` — lấy phần sau dấu / đầu tiên
1222
+ const refs = new Set();
1223
+ for (const m of txt.matchAll(/\{paths\.qc_skills_dir\}\/([A-Za-z0-9._\/-]+)/g)) {
1224
+ refs.add(m[1].replace(/[.,)`]+$/, ''));
1225
+ }
1226
+ for (const ref of refs) {
1227
+ const abs = path.join(SKILLS_QC, ref);
1228
+ if (!fs.existsSync(abs)) {
1229
+ err('R16', `${f} trỏ vào \`{paths.qc_skills_dir}/${ref}\` — KHÔNG TỒN TẠI trong skills/qc/`,
1230
+ 'Lệnh vẫn chạy, vẫn ghi file, nhưng phân tích rỗng logic và KHÔNG cờ nào bật. ' +
1231
+ 'Khôi phục skill, hoặc sửa lệnh nếu đã cố ý bỏ.');
1232
+ }
1233
+ }
1234
+ }
1235
+ }
1236
+
1237
+ if (hasMap && hasSnap) {
1238
+ let map;
1239
+ try { map = JSON.parse(fs.readFileSync(MAP_PATH, 'utf8')); }
1240
+ catch (e) { err('R16', `bin/qc-base-map.json không parse được: ${e.message}`); map = null; }
1241
+
1242
+ if (map) {
1243
+ const entries = map.map || [];
1244
+ const byUp = new Map(entries.map(e => [e.upstream, e]));
1245
+
1246
+ // liệt kê file snapshot thật
1247
+ const walk = (dir, base = '') => {
1248
+ let out = [];
1249
+ for (const n of fs.readdirSync(dir)) {
1250
+ const p = path.join(dir, n), rel = base ? `${base}/${n}` : n;
1251
+ out = out.concat(fs.statSync(p).isDirectory() ? walk(p, rel) : [rel]);
1252
+ }
1253
+ return out;
1254
+ };
1255
+ const snap = walk(SNAP_DIR).sort();
1256
+
1257
+ // ── (a) mọi file snapshot phải có entry ───────────────────────────────────
1258
+ const missing = snap.filter(r => !byUp.has(r));
1259
+ if (missing.length) {
1260
+ err('R16', `${missing.length} file trong upstream/qc-base/ KHÔNG có entry trong bin/qc-base-map.json`,
1261
+ `Bị bỏ quên trong im lặng. Thêm entry với state \`skipped\` + \`why\` nếu cố ý không lấy.\n` +
1262
+ ` ${missing.slice(0, 5).join('\n ')}${missing.length > 5 ? `\n … và ${missing.length - 5} file nữa` : ''}`);
1263
+ }
1264
+ const orphan = entries.filter(e => !snap.includes(e.upstream));
1265
+ if (orphan.length) {
1266
+ err('R16', `${orphan.length} entry trỏ vào file KHÔNG còn trong snapshot: ${orphan.map(e => e.upstream).join(', ')}`,
1267
+ 'Snapshot đã đổi mà bản đồ chưa theo — chạy lại bước dựng bản đồ.');
1268
+ }
1269
+
1270
+ // ── (b) entry `ported` phải có base_sha, và base_sha phải KHỚP file thật ──
1271
+ const sha1 = b => crypto.createHash('sha1').update(b).digest('hex');
1272
+ for (const e of entries.filter(x => x.state === 'ported')) {
1273
+ if (!e.base_sha) {
1274
+ err('R16', `entry \`${e.upstream}\` là \`ported\` nhưng THIẾU base_sha`,
1275
+ 'Không có base thì merge ba chiều lần sau mất một chiều — mọi đồng bộ thành đoán.');
1276
+ continue;
1277
+ }
1278
+ const abs = path.join(SNAP_DIR, e.upstream);
1279
+ if (!fs.existsSync(abs)) continue; // đã báo ở orphan
1280
+ const real = sha1(fs.readFileSync(abs));
1281
+ if (real !== e.base_sha) {
1282
+ err('R16', `base_sha của \`${e.upstream}\` KHÔNG khớp file snapshot`,
1283
+ `khai=${e.base_sha.slice(0, 12)}… thật=${real.slice(0, 12)}… — sha bịa, hoặc snapshot đã bị sửa tay sau khi port.`);
1284
+ }
1285
+ // frontmatter của file đích phải khai đúng cùng một sha VÀ cùng một upstream_path
1286
+ for (const t of (e.targets || [])) {
1287
+ const tp = path.join(ROOT, t);
1288
+ if (!fs.existsSync(tp)) continue; // đã báo ở (c)
1289
+ const txt = fs.readFileSync(tp, 'utf8');
1290
+ const m = /^upstream_sha: (.+)$/m.exec(txt);
1291
+ if (m && m[1].trim() !== e.base_sha) {
1292
+ err('R16', `${t} khai upstream_sha khác base_sha trong bản đồ`,
1293
+ `file=${m[1].trim().slice(0, 12)}… map=${e.base_sha.slice(0, 12)}… — hai chỗ nói về cùng một sự thật mà lệch nhau.`);
1294
+ }
1295
+ // ── (e) upstream_path trong frontmatter phải trỏ vào file snapshot CÓ THẬT ──
1296
+ //
1297
+ // Vì sao cần nhánh riêng: (b) so sha, và sha vẫn khớp khi đường dẫn bị sửa sai —
1298
+ // nên (b) mù với ca này. Ca thật (B9): một script đổi tên `DOC_GAPS.md → DOC_GAP.md`
1299
+ // chạy quét cả frontmatter, sửa luôn `upstream_path` thành tên KHÔNG tồn tại ở
1300
+ // upstream. self-check vẫn xanh; chỉ lộ ra khi kiểm tay. Mất `upstream_path` là mất
1301
+ // nửa BASE của merge ba chiều — biết "sha nào" mà không biết "của file nào".
1302
+ const mp = /^upstream_path: (.+)$/m.exec(txt);
1303
+ if (mp) {
1304
+ const declared = mp[1].trim();
1305
+ if (declared !== e.upstream) {
1306
+ err('R16', `${t} khai upstream_path "${declared}" ≠ bản đồ "${e.upstream}"`,
1307
+ 'Hai chỗ cùng khai một sự thật mà lệch nhau — sửa cho khớp.');
1308
+ }
1309
+ if (!fs.existsSync(path.join(SNAP_DIR, declared))) {
1310
+ err('R16', `${t} khai upstream_path "${declared}" — KHÔNG TỒN TẠI trong snapshot`,
1311
+ 'Đường dẫn bị sửa sai (vd một lần đổi tên hàng loạt quét cả frontmatter), ' +
1312
+ 'hoặc upstream đã đổi tên. sha có thể vẫn khớp nên nhánh (b) không bắt được.');
1313
+ }
1314
+ }
1315
+ }
1316
+ }
1317
+
1318
+ // ── (c) mọi target của entry `ported` phải tồn tại ────────────────────────
1319
+ for (const e of entries.filter(x => x.state === 'ported')) {
1320
+ for (const t of (e.targets || [])) {
1321
+ if (!fs.existsSync(path.join(ROOT, t))) {
1322
+ err('R16', `entry \`${e.upstream}\` là \`ported\` nhưng target \`${t}\` không tồn tại`,
1323
+ 'Bản đồ lỗi thời sau khi đổi tên/xoá file đích.');
1324
+ }
1325
+ }
1326
+ }
1327
+ }
1328
+ }
1329
+ }
1330
+
1185
1331
  // ── report ────────────────────────────────────────────────────────────────────
1186
1332
  const tagCount = schema.tags.length;
1187
1333
  const colCount = schema.tsv_columns.length;
@@ -1 +1 @@
1
- 0.9.1
1
+ 0.9.3
@@ -1,7 +1,7 @@
1
1
  # /propose-scenario — Đề xuất một BDD Scenario mới (cho Tester & QC)
2
2
 
3
3
  Dành cho **tester và QC** phát hiện edge case chưa được BDD hiện tại phủ (vd một gap missing-coverage
4
- `DOC_GAPS` từ `/qc-analyze`). Draft một Gherkin scenario vào **khu proposal** để
4
+ `DOC_GAP` từ `/qc-analyze`). Draft một Gherkin scenario vào **khu proposal** để
5
5
  PO/Dev review và promote.
6
6
 
7
7
  **KHÔNG sửa file `.feature` canonical.** BDD do PO/Dev sở hữu — lệnh này chỉ ghi