@educa-corp/sdd-framework 0.5.0 → 0.6.0
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/build.js +113 -19
- package/bin/gate-trace.js +464 -0
- package/bin/index.js +418 -146
- package/bin/lint-trace.js +602 -0
- package/bin/self-check.js +376 -1
- package/bin/trace-schema.json +252 -2
- package/commands/debug.md +123 -511
- package/commands/debug.tmpl +3 -0
- package/commands/define-product.md +86 -510
- package/commands/dev-gen-test.md +86 -510
- package/commands/dev-run-test.md +86 -510
- package/commands/dev-smoke-test.md +86 -510
- package/commands/extend-prd.md +89 -510
- package/commands/extend-prd.tmpl +3 -0
- package/commands/fix-bug.md +118 -509
- package/commands/generate-architecture.md +94 -515
- package/commands/generate-architecture.tmpl +3 -0
- package/commands/generate-bdd.md +85 -509
- package/commands/generate-code.md +86 -510
- package/commands/generate-design-spec.md +86 -510
- package/commands/generate-prd.md +89 -510
- package/commands/generate-prd.tmpl +3 -0
- package/commands/generate-spec-manifest.md +86 -510
- package/commands/generate-tech-docs.md +86 -510
- package/commands/learn.md +172 -496
- package/commands/learn.tmpl +70 -3
- package/commands/map-testids.md +86 -510
- package/commands/propose-scenario.md +86 -510
- package/commands/qc-analyze.md +86 -510
- package/commands/qc-design-test.md +86 -510
- package/commands/qc-plan.md +86 -510
- package/commands/qc-report.md +86 -510
- package/commands/qc-review.md +86 -510
- package/commands/qc-run-test.md +86 -510
- package/commands/refine-prd.md +99 -520
- package/commands/refine-prd.tmpl +3 -0
- package/commands/report-bug.md +86 -510
- package/commands/review-code.md +123 -511
- package/commands/review-code.tmpl +3 -0
- package/commands/review-context.md +93 -514
- package/commands/review-context.tmpl +3 -0
- package/commands/review-tech-docs.md +90 -511
- package/commands/review-tech-docs.tmpl +3 -0
- package/commands/setup-ai-first.md +166 -138
- package/commands/setup-ai-first.tmpl +72 -0
- package/commands/sync.md +50 -106
- package/commands/sync.tmpl +48 -3
- package/commands/update-framework.md +16 -103
- package/commands/update-framework.tmpl +14 -0
- package/commands/validate-traces.md +153 -511
- package/commands/validate-traces.tmpl +67 -1
- package/core/FRAMEWORK_VERSION +1 -1
- package/core/README.md +20 -0
- package/core/commands/debug.md +123 -511
- package/core/commands/define-product.md +86 -510
- package/core/commands/dev-gen-test.md +86 -510
- package/core/commands/dev-run-test.md +86 -510
- package/core/commands/dev-smoke-test.md +86 -510
- package/core/commands/extend-prd.md +89 -510
- package/core/commands/fix-bug.md +118 -509
- package/core/commands/generate-architecture.md +94 -515
- package/core/commands/generate-bdd.md +85 -509
- package/core/commands/generate-code.md +86 -510
- package/core/commands/generate-design-spec.md +86 -510
- package/core/commands/generate-prd.md +89 -510
- package/core/commands/generate-spec-manifest.md +86 -510
- package/core/commands/generate-tech-docs.md +86 -510
- package/core/commands/learn.md +172 -496
- package/core/commands/map-testids.md +86 -510
- package/core/commands/propose-scenario.md +86 -510
- package/core/commands/qc-analyze.md +86 -510
- package/core/commands/qc-design-test.md +86 -510
- package/core/commands/qc-plan.md +86 -510
- package/core/commands/qc-report.md +86 -510
- package/core/commands/qc-review.md +86 -510
- package/core/commands/qc-run-test.md +86 -510
- package/core/commands/refine-prd.md +99 -520
- package/core/commands/report-bug.md +86 -510
- package/core/commands/review-code.md +123 -511
- package/core/commands/review-context.md +93 -514
- package/core/commands/review-tech-docs.md +90 -511
- package/core/commands/setup-ai-first.md +166 -138
- package/core/commands/sync.md +50 -106
- package/core/commands/update-framework.md +16 -103
- package/core/commands/validate-traces.md +153 -511
- package/core/hooks/data-guard.js +174 -83
- package/core/hooks/settings.json +2 -1
- package/core/rules/workflow.md +30 -4
- package/core/steps/capture-lesson.md +34 -1
- package/core/steps/context-loader.md +24 -3
- package/core/steps/gate.md +92 -35
- package/core/steps/report-footer.md +23 -0
- package/core/templates/README.md +24 -1
- package/core/templates/ci/trace-gate.yml +146 -0
- package/core/templates/hooks/pre-push +61 -0
- package/docs/02-concepts/architecture.md +25 -6
- package/docs/02-concepts/traceability.md +57 -0
- package/docs/03-guides/architect.md +63 -0
- package/docs/04-reference/commands.md +1 -1
- package/docs/04-reference/model-selection.md +32 -19
- package/docs/explain/21-validate-traces.md +2 -1
- package/docs/explain/27-learn.md +5 -3
- package/hooks/data-guard.js +174 -83
- package/hooks/settings.json +2 -1
- package/package.json +5 -2
- package/rules/workflow.md +30 -4
- package/steps/capture-lesson.md +34 -1
- package/steps/context-loader.md +24 -3
- package/steps/gate.md +92 -35
- package/steps/report-footer.md +23 -0
- package/templates/README.md +24 -1
- package/templates/ci/trace-gate.yml +146 -0
- package/templates/hooks/pre-push +61 -0
- package/scripts/init.sh +0 -49
- package/scripts/upgrade.sh +0 -94
|
@@ -2,33 +2,46 @@
|
|
|
2
2
|
|
|
3
3
|
# Reference · Model Selection
|
|
4
4
|
|
|
5
|
-
> Framework **không ràng buộc model cứng** —
|
|
5
|
+
> Framework **không ràng buộc model cứng** — nó **khai báo** model đang chạy ở report cuối. Giả định LLM đủ năng lực reasoning.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
## Model
|
|
9
|
+
## Model được KHAI BÁO, không bị hỏi *(từ v0.5.1)*
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Gate Bước 0-B ghi nhận model agent đang chạy; `report-footer` in ra ở cuối mỗi lệnh:
|
|
12
12
|
|
|
13
13
|
```
|
|
14
|
-
|
|
15
|
-
Recommended : claude-opus (model Opus mới nhất)
|
|
16
|
-
Why needed : Phân tích spec, review kiến trúc, sinh code đòi hỏi
|
|
17
|
-
suy luận sâu. Model nhỏ hơn dễ bỏ sót edge case.
|
|
18
|
-
|
|
19
|
-
Đang chạy claude-opus?
|
|
20
|
-
Y — đúng → tiếp tục
|
|
21
|
-
S — bỏ qua (chấp nhận rủi ro chất lượng thấp hơn)
|
|
22
|
-
N — dừng, đổi model rồi chạy lại
|
|
14
|
+
Model: claude-opus-5
|
|
23
15
|
```
|
|
24
16
|
|
|
25
|
-
|
|
26
|
-
|---------|---------|
|
|
27
|
-
| **Y** | Tiếp tục |
|
|
28
|
-
| **S** | Tiếp tục, thêm ⚠️ vào report cuối (người dùng chấp nhận rủi ro) |
|
|
29
|
-
| **N** / khác | **Dừng** — đổi sang Opus rồi chạy lại |
|
|
17
|
+
Nếu agent biết mình không phải một model Opus:
|
|
30
18
|
|
|
31
|
-
|
|
19
|
+
```
|
|
20
|
+
Model: claude-haiku-4-5 ⚠️ lệnh này khuyến nghị Opus — model nhỏ hơn dễ bỏ sót
|
|
21
|
+
edge case, phân tích spec thiếu sót, vi phạm kiến trúc.
|
|
22
|
+
Cân nhắc chạy lại với /model → Opus trước khi dùng kết quả.
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Không có prompt nào. Không chờ. Không dừng.**
|
|
26
|
+
|
|
27
|
+
### Vì sao bỏ prompt cũ (GAPS-v3 G41)
|
|
28
|
+
|
|
29
|
+
Bản trước hiện một khối `⚙️ MODEL CHECK` rồi chờ `Y/S/N` ở **mọi** lệnh. Ba vấn đề:
|
|
30
|
+
|
|
31
|
+
| | |
|
|
32
|
+
|---|---|
|
|
33
|
+
| Hỏi sai người | Nó hỏi **người dùng** thứ mà **agent đã biết chính xác** |
|
|
34
|
+
| Không kiểm chứng được | Gõ `Y` xong vẫn đang chạy Haiku thì không gì phát hiện |
|
|
35
|
+
| Không chặn được ai | **Cả `Y` lẫn `S` đều đi tiếp** — cách duy nhất để nó dừng là tự nguyện gõ `N` |
|
|
36
|
+
|
|
37
|
+
Cái giá: một lần chặn ở mọi lệnh. Một feature đi hết pipeline dùng **20 lệnh** — 20 lần bấm
|
|
38
|
+
cho một tín hiệu tự-khai không kiểm chứng được. Và chính cái giá đó **làm mòn CHECKPOINT**,
|
|
39
|
+
cổng có giá trị thật ngay sau nó: bấm `Y` 40 lần một feature thì `Y` thành phản xạ.
|
|
40
|
+
|
|
41
|
+
Khai báo ở footer **mạnh hơn** hỏi: đúng nguồn (agent tự khai), và nằm **cạnh kết quả** để
|
|
42
|
+
người đọc cân nhắc có nên tin, thay vì nằm trước khi có kết quả để bấm cho xong.
|
|
43
|
+
|
|
44
|
+
> Sub-agent (`_agent_mode`) bỏ qua bước này — orchestrator đã ghi nhận rồi.
|
|
32
45
|
|
|
33
46
|
---
|
|
34
47
|
|
|
@@ -56,7 +69,7 @@ Model Claude mới nhất tại thời điểm viết:
|
|
|
56
69
|
|
|
57
70
|
| Model | ID |
|
|
58
71
|
|-------|-----|
|
|
59
|
-
| Opus
|
|
72
|
+
| Opus 5 | `claude-opus-5` |
|
|
60
73
|
| Sonnet 5 | `claude-sonnet-5` |
|
|
61
74
|
| Haiku 4.5 | `claude-haiku-4-5-20251001` |
|
|
62
75
|
| Fable 5 | `claude-fable-5` |
|
|
@@ -39,7 +39,8 @@ Traceability chỉ có giá trị khi kiểm được. Command cho bức tranh t
|
|
|
39
39
|
| 2 | DRIFT | có code + `spec_ver != gen_ver` |
|
|
40
40
|
| 3 | GAP | có code + `test_count == —/0` |
|
|
41
41
|
| 4 | OK | version khớp + có code + có test |
|
|
42
|
-
3. Dựng dashboard: `dev_selftest` (DEV smoke) **và** `qc_status` (QC chính thức) hiển thị cạnh nhau — **không merge**; cột `qc_owner` + `qc_blocked_by` ("Waiting on");
|
|
42
|
+
3. Dựng dashboard: `dev_selftest` (DEV smoke) **và** `qc_status` (QC chính thức) hiển thị cạnh nhau — **không merge**; cột `qc_owner` + `qc_blocked_by` ("Waiting on"); và **hai trục chia nhóm**: **`by_service`** (coverage theo từng đội — cột `service`) · **`by_platform`** (coverage theo `web`/`app`/`system`).
|
|
43
|
+
> `by_platform` trả lời *"web xong bao nhiêu %, system xong bao nhiêu %"* — câu thường ngày khi làm FE và BE song song. Trước v0.5.1 không trả lời được **từ `summary`**: `by_service` là bảng chia nhóm duy nhất, mà cột `service` là `—` ở mọi row của dự án single-service ⇒ nó gộp tất cả vào một ô.
|
|
43
44
|
4. **Lọc báo động oan (Step 4/5).** PRD và tech-doc là tài liệu **gộp** nhiều UC nhưng chỉ **một** số version — thêm UC7 làm mọi UC cũ lệch số dù không đổi một chữ. Đọc **scope của row changelog**: UC có trong danh sách → `PRD_DRIFT` 🟠 · không có → `PRD_STALE_REF` ⓘ (sạch bằng `--realign-*`) · row **mơ hồ** → 🟠 cho mọi UC (lưới an toàn).
|
|
44
45
|
5. **Step 5d — design-spec drift** *(chỉ FE/App)*: 2 chiều, design-spec→BDD và design-spec→code.
|
|
45
46
|
6. **Step 7b — hàng đợi**: đếm PRD change request còn `Open` kèm **số ngày chờ** (hàng đợi duy nhất không có lệnh nào quét lại mỗi lần chạy).
|
package/docs/explain/27-learn.md
CHANGED
|
@@ -46,17 +46,19 @@
|
|
|
46
46
|
|
|
47
47
|
## Cơ chế đặc biệt
|
|
48
48
|
|
|
49
|
-
- **Lesson là ràng buộc cứng** — context-loader Bước 6.7 nạp
|
|
49
|
+
- **Lesson là ràng buộc cứng** — context-loader Bước 6.7 nạp lesson `Status: active` có `category` khớp lệnh (+ `general`); output vi phạm → AI sửa trước khi trình + ghi `L-NNN` đã áp.
|
|
50
50
|
- **Dùng chung `capture-lesson`** với `/review-code`, `/fix-bug`, `/debug` — lesson sinh tự nhiên từ chỗ phát hiện.
|
|
51
|
+
- **`/learn --review` — đường RA** *(từ v0.5.1, GAPS-v3 G46)*. Retire = đổi `Status` + ghi lý do, **không xoá** (lesson retired ở lại làm lịch sử). Tiêu chí 🔴 **kiểm được bằng máy**: `Scope` là file glob mà glob không còn khớp file nào ⇒ code lesson canh đã không tồn tại. `Scope` là `all`/domain thì không tự kiểm được — chỉ liệt kê theo tuổi để người quyết.
|
|
51
52
|
|
|
52
53
|
---
|
|
53
54
|
|
|
54
55
|
## 👓 Góc nhìn tối ưu
|
|
55
56
|
|
|
56
57
|
- **Bộ nhớ dự án cốt lõi** — nhưng phụ thuộc con người chủ động `/learn`. Lesson không ghi = không nhớ.
|
|
57
|
-
- **Dedup L3 phụ thuộc AI so khớp** — lesson gần giống có thể lọt thành trùng
|
|
58
|
+
- **Dedup L3 phụ thuộc AI so khớp** — lesson gần giống có thể lọt thành trùng.
|
|
58
59
|
- **Scope/category filtering** quyết định lesson nào nạp — nếu gắn sai, guardrail không kích hoạt đúng lúc.
|
|
59
|
-
-
|
|
60
|
+
- ~~**Không có cơ chế "retire" lesson lỗi thời** — file chỉ lớn dần.~~ ✅ **Đã có từ v0.5.1** (`/learn --review`). *Ghi chú này từng đứng ở đây trước khi GAPS-v3 rà tới — nó đã đúng, chỉ chưa ai làm.*
|
|
61
|
+
- **Vẫn phụ thuộc con người chạy `--review`** — không gì tự retire. Cố ý: tự bỏ một guardrail trong im lặng là đúng thứ framework này tồn tại để chống. Recap cảnh báo khi ≥40 lesson active.
|
|
60
62
|
|
|
61
63
|
---
|
|
62
64
|
|
package/hooks/data-guard.js
CHANGED
|
@@ -2,28 +2,90 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* data-guard.js — Claude Code PreToolUse Hook
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* sensitive files (credentials, secrets, private keys, .env, etc.)
|
|
5
|
+
* Chặn AI đọc/ghi/thực thi trên file nhạy cảm (credential, secret, private key, .env…).
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
* (
|
|
7
|
+
* Cài: copy vào project + đăng ký trong .claude/settings.json
|
|
8
|
+
* (mẫu: hooks/settings.json — hoặc dùng `npx @educa-corp/sdd-framework --hooks`)
|
|
10
9
|
*
|
|
11
|
-
* Exit
|
|
12
|
-
* 0 =
|
|
13
|
-
* 2 =
|
|
10
|
+
* Exit code:
|
|
11
|
+
* 0 = cho phép
|
|
12
|
+
* 2 = chặn cứng (Claude Code hiểu mã này là block)
|
|
13
|
+
*
|
|
14
|
+
* ── VÌ SAO BẢN NÀY KHÁC BẢN ĐẦU (GAPS-v3 G42) ────────────────────────────────
|
|
15
|
+
*
|
|
16
|
+
* Bản đầu có ba lỗi độc lập; hai trong số đó chặn đúng công việc mà framework tồn
|
|
17
|
+
* tại để làm, và cái còn lại làm lời hứa bảo mật không đúng:
|
|
18
|
+
*
|
|
19
|
+
* (a) matcher thiếu `Grep` — `Grep` với output_mode:"content" trên `.env` TRẢ VỀ
|
|
20
|
+
* nội dung file và không đi qua hook. Nên câu "AI will be blocked from reading
|
|
21
|
+
* .env" là sai.
|
|
22
|
+
* (b) pattern soi CẢ PATH, không neo. `/password/i` khớp
|
|
23
|
+
* `specs/auth/forgot-password/UC1.feature` và `src/auth/PasswordResetController.java`
|
|
24
|
+
* ⇒ một framework sinh code từ spec KHÔNG LÀM NỔI feature auth khi bật hook
|
|
25
|
+
* của chính nó.
|
|
26
|
+
* (c) Bash soi cả command string ⇒ `git commit -m "feat: password reset UC1"` bị
|
|
27
|
+
* chặn — đúng câu commit mà các lệnh hướng dẫn chạy ở cuối pipeline.
|
|
28
|
+
*
|
|
29
|
+
* Cả hai đường đều dẫn tới cùng một kết cục: người dùng TẮT HOOK, và mất luôn phần
|
|
30
|
+
* bảo vệ thật. Một guard bị tắt bảo vệ bằng không.
|
|
31
|
+
*
|
|
32
|
+
* ── MÔ HÌNH MỚI ──────────────────────────────────────────────────────────────
|
|
33
|
+
*
|
|
34
|
+
* Secret sống trong file CẤU HÌNH/DỮ LIỆU, không sống trong file MÃ NGUỒN.
|
|
35
|
+
* `secrets.json` là kho bí mật; `PasswordResetController.java` là code.
|
|
36
|
+
* Nên pattern chung (secret/credential/password/token/api-key) chỉ áp cho file
|
|
37
|
+
* KHÔNG phải mã nguồn. Pattern cứng (.env, .pem, .key…) áp cho mọi file.
|
|
38
|
+
* Cộng thêm allowlist thư mục làm việc của framework (specs/, .trace/, docs/…).
|
|
14
39
|
*/
|
|
15
40
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
//
|
|
41
|
+
// ── Thư mục làm việc của framework — không bao giờ chứa secret thật ───────────
|
|
42
|
+
// Tên feature ("forgot-password", "api-key-rotation") hay khớp pattern chung, và
|
|
43
|
+
// đây chính là chỗ ca (b) nổ. Cho qua TRƯỚC khi soi.
|
|
44
|
+
const SAFE_DIR_PREFIXES = [
|
|
45
|
+
'specs/', '.trace/', '.trace-mirror/', '.living-docs/',
|
|
46
|
+
'docs/', '.agent/', 'feedback/', '.claude/',
|
|
47
|
+
];
|
|
19
48
|
|
|
20
|
-
|
|
21
|
-
|
|
49
|
+
// ── Đuôi file MÃ NGUỒN — pattern chung không áp ───────────────────────────────
|
|
50
|
+
// Một file .java/.ts/.dart tên gì đi nữa cũng là code, không phải kho secret.
|
|
51
|
+
// (Pattern CỨNG bên dưới vẫn áp — nhưng .pem/.key không nằm trong danh sách này.)
|
|
52
|
+
const CODE_EXT = new Set([
|
|
53
|
+
'java', 'kt', 'kts', 'scala', 'groovy',
|
|
54
|
+
'ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs', 'vue', 'svelte',
|
|
55
|
+
'py', 'go', 'rs', 'rb', 'php', 'cs', 'fs', 'vb',
|
|
56
|
+
'swift', 'm', 'mm', 'dart', 'c', 'h', 'cpp', 'hpp', 'cc',
|
|
57
|
+
'sql', 'graphql', 'proto', 'feature', 'md', 'mdx', 'txt',
|
|
58
|
+
'html', 'css', 'scss', 'less', 'tsv', 'csv',
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
// ── Pattern CỨNG — áp cho mọi file, soi TÊN FILE ─────────────────────────────
|
|
62
|
+
const SENSITIVE_NAME_HARD = [
|
|
22
63
|
/^\.env$/i,
|
|
23
64
|
/^\.env\./i,
|
|
24
65
|
/\.env$/i,
|
|
25
66
|
|
|
26
|
-
|
|
67
|
+
/\.key$/i,
|
|
68
|
+
/\.pem$/i,
|
|
69
|
+
/\.p12$/i,
|
|
70
|
+
/\.pfx$/i,
|
|
71
|
+
/\.jks$/i,
|
|
72
|
+
/\.keystore$/i,
|
|
73
|
+
/^id_rsa$/i,
|
|
74
|
+
/^id_ed25519$/i,
|
|
75
|
+
|
|
76
|
+
/^application-(prod|production|staging)\.(yml|yaml|properties)$/i,
|
|
77
|
+
/^appsettings\.(Production|Staging)\.json$/i,
|
|
78
|
+
/^database\.yml$/i,
|
|
79
|
+
/^master\.key$/i,
|
|
80
|
+
/^oauth-private\.key$/i,
|
|
81
|
+
/^credentials$/i,
|
|
82
|
+
/^\.npmrc$/i,
|
|
83
|
+
/^\.pypirc$/i,
|
|
84
|
+
/^\.netrc$/i,
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
// ── Pattern CHUNG — chỉ áp cho file KHÔNG phải mã nguồn, soi TÊN FILE ────────
|
|
88
|
+
const SENSITIVE_NAME_SOFT = [
|
|
27
89
|
/secret/i,
|
|
28
90
|
/credential/i,
|
|
29
91
|
/password/i,
|
|
@@ -32,60 +94,84 @@ const SENSITIVE_PATH_PATTERNS = [
|
|
|
32
94
|
/api[_-]?key/i,
|
|
33
95
|
/access[_-]?token/i,
|
|
34
96
|
/auth[_-]?token/i,
|
|
97
|
+
];
|
|
35
98
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// Framework-specific prod configs
|
|
45
|
-
/application-(prod|production|staging)\.(yml|yaml|properties)$/i,
|
|
46
|
-
/appsettings\.(Production|Staging)\.json$/i,
|
|
47
|
-
/database\.yml$/i,
|
|
48
|
-
/config\/master\.key$/i,
|
|
49
|
-
/storage\/oauth-private\.key$/i,
|
|
50
|
-
|
|
51
|
-
// Secret directories
|
|
52
|
-
/^secrets\//i,
|
|
53
|
-
/\/secrets\//i,
|
|
54
|
-
/^\.secrets\//i,
|
|
99
|
+
// ── Pattern THƯ MỤC — soi cả path ────────────────────────────────────────────
|
|
100
|
+
const SENSITIVE_DIR = [
|
|
101
|
+
/(^|\/)secrets?\//i,
|
|
102
|
+
/(^|\/)\.secrets?\//i,
|
|
103
|
+
/(^|\/)\.ssh\//i,
|
|
104
|
+
/(^|\/)\.aws\//i,
|
|
105
|
+
/(^|\/)\.gnupg\//i,
|
|
55
106
|
];
|
|
56
107
|
|
|
57
|
-
|
|
108
|
+
// ── Lệnh bash nguy hiểm bất kể path ──────────────────────────────────────────
|
|
109
|
+
const SENSITIVE_BASH = [
|
|
58
110
|
/\bprintenv\b/i,
|
|
59
|
-
/\benv\b
|
|
60
|
-
/
|
|
61
|
-
|
|
62
|
-
|
|
111
|
+
/\benv\b\s*(\||$)/i,
|
|
112
|
+
/kubectl\s+get\s+secrets?\b[^|]*-o\s*(yaml|json)/i,
|
|
113
|
+
/\baws\s+secretsmanager\s+get-secret-value/i,
|
|
114
|
+
/\bgcloud\s+secrets\s+versions\s+access/i,
|
|
115
|
+
/\bvault\s+(read|kv\s+get)\b/i,
|
|
116
|
+
// `docker inspect` KHÔNG chặn cả cụm (lệnh debug thường dùng) — chỉ chặn khi nó
|
|
117
|
+
// thực sự moi Env ra. Bản đầu chặn cả cụm và đó là ma sát không đổi lấy gì.
|
|
118
|
+
/docker\s+inspect[^|]*\.Config\.Env/i,
|
|
63
119
|
];
|
|
64
120
|
|
|
65
|
-
// ── Helpers
|
|
121
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
function normalize(p) {
|
|
124
|
+
return String(p).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function extOf(base) {
|
|
128
|
+
const i = base.lastIndexOf('.');
|
|
129
|
+
return i > 0 ? base.slice(i + 1).toLowerCase() : '';
|
|
130
|
+
}
|
|
66
131
|
|
|
67
132
|
function isSensitivePath(filePath) {
|
|
68
133
|
if (!filePath) return false;
|
|
69
|
-
const
|
|
70
|
-
|
|
134
|
+
const p = normalize(filePath);
|
|
135
|
+
|
|
136
|
+
// Vùng làm việc của framework — spec/trace/doc không chứa secret thật, và tên
|
|
137
|
+
// feature ở đó hay khớp pattern chung. Bỏ cả tiền tố tuyệt đối trước khi so.
|
|
138
|
+
const relish = p.replace(/^([a-zA-Z]:)?\/+/, '');
|
|
139
|
+
if (SAFE_DIR_PREFIXES.some(d => relish.startsWith(d) || relish.includes('/' + d))) return false;
|
|
140
|
+
|
|
141
|
+
if (SENSITIVE_DIR.some(re => re.test(p))) return true;
|
|
142
|
+
|
|
143
|
+
const base = p.split('/').pop() || '';
|
|
144
|
+
if (SENSITIVE_NAME_HARD.some(re => re.test(base))) return true;
|
|
145
|
+
|
|
146
|
+
// Pattern chung: bỏ qua mã nguồn. Đây là chỗ bản đầu chặn PasswordResetController.java.
|
|
147
|
+
if (CODE_EXT.has(extOf(base))) return false;
|
|
148
|
+
return SENSITIVE_NAME_SOFT.some(re => re.test(base));
|
|
71
149
|
}
|
|
72
150
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
151
|
+
/**
|
|
152
|
+
* Tách các token TRÔNG GIỐNG PATH ra khỏi một command line.
|
|
153
|
+
* Bản đầu soi cả chuỗi lệnh nên `git commit -m "feat: password reset"` bị chặn.
|
|
154
|
+
*/
|
|
155
|
+
function pathishTokens(command) {
|
|
156
|
+
return String(command)
|
|
157
|
+
.split(/[\s;|&()<>]+/)
|
|
158
|
+
.map(t => t.replace(/^["']|["']$/g, ''))
|
|
159
|
+
.filter(t => t && !t.startsWith('-') && /[\/.]/.test(t) && !/^-{1,2}/.test(t));
|
|
76
160
|
}
|
|
77
161
|
|
|
78
|
-
function block(reason) {
|
|
79
|
-
console.error(`\n🔒 DATA GUARD — BLOCKED\n${reason}
|
|
80
|
-
console.error(
|
|
81
|
-
console.error('
|
|
162
|
+
function block(reason, detail) {
|
|
163
|
+
console.error(`\n🔒 DATA GUARD — BLOCKED\n${reason}`);
|
|
164
|
+
if (detail) console.error(detail);
|
|
165
|
+
console.error('\nCần giá trị cấu hình? Mô tả thứ bạn cần mà KHÔNG đưa secret thật;');
|
|
166
|
+
console.error('dùng placeholder trong code sinh ra.');
|
|
167
|
+
console.error('Nghĩ đây là chặn nhầm? Vùng specs/ .trace/ docs/ .agent/ luôn được cho qua,');
|
|
168
|
+
console.error('và file mã nguồn (.java/.ts/.dart…) không bị pattern chung đụng tới.\n');
|
|
82
169
|
process.exit(2);
|
|
83
170
|
}
|
|
84
171
|
|
|
85
|
-
// ── Main
|
|
172
|
+
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
86
173
|
|
|
87
174
|
let rawInput = '';
|
|
88
|
-
|
|
89
175
|
process.stdin.setEncoding('utf8');
|
|
90
176
|
process.stdin.on('data', chunk => { rawInput += chunk; });
|
|
91
177
|
process.stdin.on('end', () => {
|
|
@@ -93,49 +179,54 @@ process.stdin.on('end', () => {
|
|
|
93
179
|
try {
|
|
94
180
|
input = JSON.parse(rawInput);
|
|
95
181
|
} catch {
|
|
96
|
-
//
|
|
182
|
+
// Fail-open CÓ CHỦ Ý: hook parse lỗi mà chặn hết thì làm cả project đứng máy.
|
|
183
|
+
// Nhưng không im lặng — im lặng là cách một guard chết mà không ai biết.
|
|
184
|
+
console.error('⚠️ data-guard: không parse được input hook — CHO QUA (fail-open).');
|
|
97
185
|
process.exit(0);
|
|
98
186
|
}
|
|
99
187
|
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
// ── Write tool ────────────────────────────────────────────────────────
|
|
112
|
-
if (toolName === 'Write') {
|
|
113
|
-
const filePath = toolInput.file_path || '';
|
|
114
|
-
if (isSensitivePath(filePath)) {
|
|
115
|
-
block(`Attempted to WRITE to sensitive file: ${filePath}`);
|
|
188
|
+
const tool = input.tool_name || '';
|
|
189
|
+
const arg = input.tool_input || {};
|
|
190
|
+
|
|
191
|
+
switch (tool) {
|
|
192
|
+
case 'Read':
|
|
193
|
+
case 'Write':
|
|
194
|
+
case 'Edit':
|
|
195
|
+
case 'NotebookEdit': {
|
|
196
|
+
const f = arg.file_path || arg.notebook_path || '';
|
|
197
|
+
if (isSensitivePath(f)) block(`Thao tác ${tool} trên file nhạy cảm: ${f}`);
|
|
198
|
+
break;
|
|
116
199
|
}
|
|
117
|
-
}
|
|
118
200
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
201
|
+
// Grep ĐỌC ĐƯỢC NỘI DUNG (output_mode: "content"). Thiếu nhánh này thì mọi thứ
|
|
202
|
+
// ở trên chỉ là cửa trước, còn cửa sau mở toang — chính là lỗi (a) của G42.
|
|
203
|
+
case 'Grep': {
|
|
204
|
+
const target = arg.path || '';
|
|
205
|
+
if (target && isSensitivePath(target)) {
|
|
206
|
+
block(`Grep nhắm vào path nhạy cảm: ${target}`);
|
|
207
|
+
}
|
|
208
|
+
if (arg.glob && isSensitivePath(String(arg.glob).replace(/\*/g, 'x'))) {
|
|
209
|
+
block(`Grep dùng glob nhắm vào file nhạy cảm: ${arg.glob}`);
|
|
210
|
+
}
|
|
211
|
+
// Đang đi TÌM chính chuỗi secret — chặn kèm lý do khác hẳn.
|
|
212
|
+
if (arg.pattern && /\b(BEGIN [A-Z ]*PRIVATE KEY|aws_secret_access_key|sk-[A-Za-z0-9]{16,})/i
|
|
213
|
+
.test(String(arg.pattern))) {
|
|
214
|
+
block(`Grep đang tìm chính giá trị bí mật: ${String(arg.pattern).slice(0, 40)}…`);
|
|
215
|
+
}
|
|
216
|
+
break;
|
|
124
217
|
}
|
|
125
|
-
}
|
|
126
218
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
219
|
+
case 'Bash': {
|
|
220
|
+
const cmd = arg.command || '';
|
|
221
|
+
if (SENSITIVE_BASH.some(re => re.test(cmd))) {
|
|
222
|
+
block(`Lệnh moi secret: ${cmd.slice(0, 120)}`);
|
|
223
|
+
}
|
|
224
|
+
// CHỈ soi token trông giống path — không soi cả chuỗi lệnh (lỗi (c) của G42).
|
|
225
|
+
const hit = pathishTokens(cmd).find(isSensitivePath);
|
|
226
|
+
if (hit) block(`Lệnh bash chạm file nhạy cảm: ${hit}`, ` (trong: ${cmd.slice(0, 120)})`);
|
|
227
|
+
break;
|
|
136
228
|
}
|
|
137
229
|
}
|
|
138
230
|
|
|
139
|
-
// Allow all other tool calls
|
|
140
231
|
process.exit(0);
|
|
141
232
|
});
|
package/hooks/settings.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_comment": "Claude Code hook registration template. Copy this content into your project's .claude/settings.json",
|
|
3
|
+
"_matcher_note": "Grep BẮT BUỘC có trong matcher: Grep với output_mode:content ĐỌC ĐƯỢC nội dung file, nên thiếu nó thì việc chặn Read chỉ là cửa trước (GAPS-v3 G42a).",
|
|
3
4
|
"_docs": "https://docs.anthropic.com/claude/claude-code/hooks",
|
|
4
5
|
|
|
5
6
|
"hooks": {
|
|
6
7
|
"PreToolUse": [
|
|
7
8
|
{
|
|
8
|
-
"matcher": "Read|Write|Edit|Bash",
|
|
9
|
+
"matcher": "Read|Write|Edit|Grep|NotebookEdit|Bash",
|
|
9
10
|
"hooks": [
|
|
10
11
|
{
|
|
11
12
|
"type": "command",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@educa-corp/sdd-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Spec Driven Development workflow framework for Claude Code",
|
|
5
5
|
"bin": {
|
|
6
6
|
"sdd-framework": "./bin/index.js"
|
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
"dev": "node bin/build.js && node bin/index.js --init",
|
|
13
13
|
"prepack": "node -e \"const fs=require('fs'); ['README.md','PUBLISHING.md','SETUP_GUIDE.md'].forEach(f=>{ if(fs.existsSync(f)) fs.renameSync(f,'__'+f); });\"",
|
|
14
14
|
"postpack": "node -e \"const fs=require('fs'); ['README.md','PUBLISHING.md','SETUP_GUIDE.md'].forEach(f=>{ if(fs.existsSync('__'+f)) fs.renameSync('__'+f,f); });\"",
|
|
15
|
-
"self-check": "node bin/self-check.js"
|
|
15
|
+
"self-check": "node bin/self-check.js",
|
|
16
|
+
"test": "node test/run.js",
|
|
17
|
+
"lint-trace": "node bin/lint-trace.js",
|
|
18
|
+
"gate-trace": "node bin/gate-trace.js"
|
|
16
19
|
},
|
|
17
20
|
"files": [
|
|
18
21
|
"bin/",
|
package/rules/workflow.md
CHANGED
|
@@ -7,10 +7,26 @@
|
|
|
7
7
|
|
|
8
8
|
## Checkpoints
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
Ba mức, định nghĩa đầy đủ ở `steps/gate.md` Bước 3a — **đây chỉ là bản tóm tắt, gate là nguồn**:
|
|
11
|
+
|
|
12
|
+
| Mức | Lệnh nào | `--yes` bỏ qua? |
|
|
13
|
+
|---|---|:---:|
|
|
14
|
+
| **Không chặn** | read-only (`/review-code` · `/validate-traces` · `/debug` · `/review-context` · `/review-tech-docs`) | — |
|
|
15
|
+
| **Chặn thường** | mọi lệnh sinh/sửa artifact | ✅ |
|
|
16
|
+
| **Chặn CỨNG** | ghi đè file đã có · `--resume` · migrate · prune | ❌ |
|
|
17
|
+
|
|
18
|
+
- CHECKPOINT phải nêu **target đã phân giải**, và **mọi cờ 🔴/⚠️ mà context-loader đã tính**
|
|
19
|
+
(`active_service = unresolved`, `Status ≠ FULL`, CLAUDE.md thiếu, target resolve bằng wildcard).
|
|
20
|
+
- **KHÔNG lặp lại** những gì `[CTX LOADED]` vừa in ngay phía trên. Sạch hết thì CHECKPOINT
|
|
21
|
+
chỉ hai dòng.
|
|
22
|
+
- `--yes` bỏ qua *chặn thường*, **không** bỏ qua *chặn cứng*, và **không** tắt việc in cờ.
|
|
23
|
+
|
|
24
|
+
> **Vì sao ba mức thay vì "always show" (G41):** bản cũ viết *"**Always** show a CHECKPOINT"*
|
|
25
|
+
> rồi ngay dòng sau lại cấp một ngoại lệ cho lệnh read-only — mà `gate.md` **không hề thực thi**
|
|
26
|
+
> ngoại lệ đó. Hai file cùng được nạp vào mọi lệnh và nói ngược nhau. Cộng thêm: cổng luôn in
|
|
27
|
+
> ra một bảng giống hệt nhau, 20 lần cho một feature, nên `Y` thành phản xạ và cổng hỏng **âm
|
|
28
|
+
> thầm** — vẫn hiện, vẫn được trả lời, chỉ là không ai đọc. Cổng chỉ ồn khi thật sự có chuyện
|
|
29
|
+
> thì mới được đọc.
|
|
14
30
|
|
|
15
31
|
## Scope Control
|
|
16
32
|
|
|
@@ -20,9 +36,19 @@
|
|
|
20
36
|
|
|
21
37
|
## Trace Contract
|
|
22
38
|
|
|
39
|
+
> **Phạm vi:** mục này áp cho **repo framework**. Ở project consumer, `.agent/` là mirror sinh
|
|
40
|
+
> ra và `bin/` không được cài — contract ở đó là **read-only**: thấy lệch thì **báo**, đừng tự
|
|
41
|
+
> sửa (xem `.agent/README.md`). Việc duy nhất chạy được ở project là **kiểm sổ trace**:
|
|
42
|
+
> `npx @educa-corp/sdd-framework --lint-trace`.
|
|
43
|
+
|
|
23
44
|
- Contract trace (field `@trace.*`, cột `.tsv`, path pattern, giá trị enum) có **một
|
|
24
45
|
nguồn-sự-thật máy đọc**: `bin/trace-schema.json`. Bản cho người đọc:
|
|
25
46
|
`docs/04-reference/trace-schema.md` — giữ hai file đồng bộ.
|
|
47
|
+
- **Canh contract ≠ canh dữ liệu.** `bin/self-check.js` đọc **file lệnh** và kiểm *"lệnh có gọi
|
|
48
|
+
đúng tên cột không"* — nó không bao giờ mở một `.tsv` thật. `bin/lint-trace.js` mở sổ thật.
|
|
49
|
+
Cần cả hai: sổ 24 cột được ghi **bằng tay**, hàng chục lần mỗi feature; một dấu tab thiếu ở
|
|
50
|
+
ô 17 dồn mọi ô sau đó sang trái, ô 21 `status` nhận một ngày tháng, và **không cờ nào bật**.
|
|
51
|
+
Thêm cột/vocabulary mới → khai binding cho `lint-trace` **ngay**; R8 fail build nếu quên.
|
|
26
52
|
- Đổi contract (thêm/bỏ/đổi nghĩa một field, path, hay giá trị enum) → **sửa
|
|
27
53
|
`bin/trace-schema.json` TRƯỚC**, rồi mới sửa lệnh. `npm run build` chạy
|
|
28
54
|
`bin/self-check.js` và **fail** nếu lệnh lệch schema.
|
package/steps/capture-lesson.md
CHANGED
|
@@ -47,7 +47,7 @@ Nếu `lessons_path` chưa tồn tại, tạo file với header sau trước:
|
|
|
47
47
|
> Các lỗi AI KHÔNG được lặp lại trong dự án này. Được nạp bởi context-loader ở đầu
|
|
48
48
|
> mỗi lệnh và coi như ràng buộc cứng (cùng mức ưu tiên với coding standards trong CLAUDE.md).
|
|
49
49
|
> Thêm bằng /learn, hoặc chấp nhận prompt trong /review-code, /fix-bug, /debug.
|
|
50
|
-
> Commit file này để cả team dùng chung guardrail.
|
|
50
|
+
> Rà lại định kỳ bằng `/learn --review`. Commit file này để cả team dùng chung guardrail.
|
|
51
51
|
|
|
52
52
|
| Category | Áp dụng cho |
|
|
53
53
|
|----------|-----------|
|
|
@@ -58,6 +58,9 @@ Nếu `lessons_path` chưa tồn tại, tạo file với header sau trước:
|
|
|
58
58
|
| prd | output của /generate-prd, /refine-prd |
|
|
59
59
|
| general | mọi lệnh |
|
|
60
60
|
|
|
61
|
+
**Status:** `active` = đang là ràng buộc cứng · `retired` = đã hết đúng, GIỮ LẠI làm lịch sử
|
|
62
|
+
nhưng context-loader **không nạp nữa**. Lesson không ghi `Status` được coi là `active`.
|
|
63
|
+
|
|
61
64
|
---
|
|
62
65
|
```
|
|
63
66
|
|
|
@@ -65,6 +68,7 @@ Chèn lesson mới ngay dưới dấu phân cách `---` (**mới nhất lên đ
|
|
|
65
68
|
|
|
66
69
|
```markdown
|
|
67
70
|
### L-{NNN} — [{category}] {title}
|
|
71
|
+
- **Status**: active
|
|
68
72
|
- **Date**: {hôm nay YYYY-MM-DD}
|
|
69
73
|
- **Scope**: {scope}
|
|
70
74
|
- **Mistake**: {mistake}
|
|
@@ -73,6 +77,35 @@ Chèn lesson mới ngay dưới dấu phân cách `---` (**mới nhất lên đ
|
|
|
73
77
|
|
|
74
78
|
```
|
|
75
79
|
|
|
80
|
+
### Retire — đường ra của một lesson *(GAPS-v3 G46)*
|
|
81
|
+
|
|
82
|
+
Một lesson là **giả thuyết rằng AI sẽ lặp lại một lỗi**. Giả thuyết đó hết đúng khi code nó canh
|
|
83
|
+
không còn tồn tại, hoặc khi quy ước dự án đã đổi. Lúc đó nó phải bị **hạ xuống**, không được giữ.
|
|
84
|
+
|
|
85
|
+
Retire = đổi `Status` và ghi lý do — **KHÔNG xoá dòng**:
|
|
86
|
+
|
|
87
|
+
```markdown
|
|
88
|
+
### L-003 — [code-gen] Dùng WebClient thay RestTemplate
|
|
89
|
+
- **Status**: retired
|
|
90
|
+
- **Retired**: 2027-03-15 — project đổi tầng HTTP, RestTemplate không còn trong repo
|
|
91
|
+
- **Date**: 2026-08-19
|
|
92
|
+
…giữ nguyên phần còn lại…
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Giữ lại vì nó là **lịch sử**: người sau đọc được *"vì sao dự án này từng có luật đó"*, thứ mà xoá
|
|
96
|
+
đi là mất vĩnh viễn.
|
|
97
|
+
|
|
98
|
+
> **Vì sao cần đường ra:** trước G46 file này **chỉ có đường vào**. Lesson được nạp làm *"ràng buộc
|
|
99
|
+
> cứng, cùng mức ưu tiên với CLAUDE.md"* — vĩnh viễn, kể cả khi code nó canh đã bị xoá. Không cờ
|
|
100
|
+
> nào nhắc, không lệnh nào gỡ; cách duy nhất là có người tự nhớ ra rồi xoá tay.
|
|
101
|
+
> Đây đúng lớp lỗi của **G28** (*"giữ một `pass` đã hết hiệu lực là báo cáo sai"*) và luật
|
|
102
|
+
> `rules/workflow.md` §Trace Contract — **"làm mất hiệu lực ≠ ghi đè"** — chỉ là ở hàng đợi này
|
|
103
|
+
> chưa ai áp luật đó.
|
|
104
|
+
>
|
|
105
|
+
> **Ba trong bốn lệnh ghi lesson là lệnh phản ứng khi có sự cố** (`/review-code`, `/fix-bug`,
|
|
106
|
+
> `/debug`), nên file phình nhanh nhất đúng lúc dự án đang trục trặc — và lesson sinh ra lúc đó
|
|
107
|
+
> hay gắn với một sự cố cụ thể hơn là một quy tắc bền.
|
|
108
|
+
|
|
76
109
|
## L5 — Xác nhận
|
|
77
110
|
|
|
78
111
|
In: `📝 Đã ghi lesson {id} → {lessons_path} ([{category}] {title})`
|
package/steps/context-loader.md
CHANGED
|
@@ -334,13 +334,33 @@ Phân giải path file lessons:
|
|
|
334
334
|
- Else mặc định `specs/domain-knowledge/lessons-learned.md`
|
|
335
335
|
- Ở chế độ umbrella/service (khi `service_root` được set), nếu `paths.lessons_file` chưa set, mặc định `{service_root}/.agent/project-lessons.md`
|
|
336
336
|
|
|
337
|
-
Nếu file tồn tại,
|
|
337
|
+
Nếu file tồn tại, **LỌC TRƯỚC KHI NẠP** — chỉ giữ lesson thoả **cả hai**:
|
|
338
|
+
|
|
339
|
+
1. **`Status: active`** (hoặc **không có** field `Status` → lesson cũ, coi là `active`)
|
|
340
|
+
2. **`category` khớp lệnh đang chạy**, hoặc `category: general`
|
|
341
|
+
|
|
342
|
+
Số còn lại mới nạp làm **GUARDRAIL ĐANG HOẠT ĐỘNG** cho phiên:
|
|
338
343
|
- Coi **Rule** của mỗi lesson là ràng buộc cứng — cùng mức ưu tiên với coding standards trong CLAUDE.md (Bước 3).
|
|
339
|
-
- Trước khi sinh hoặc sửa bất kỳ artifact nào (PRD, BDD, tech-doc, code, test), đối chiếu output với
|
|
344
|
+
- Trước khi sinh hoặc sửa bất kỳ artifact nào (PRD, BDD, tech-doc, code, test), đối chiếu output với lesson đã nạp có **`scope` khớp target** (domain / file glob).
|
|
340
345
|
- Nếu output sinh ra vi phạm một lesson → sửa **trước khi** trình bày, và ghi rõ lesson nào (`L-NNN`) đã được áp dụng.
|
|
341
346
|
|
|
347
|
+
Ghi lại **hai** con số cho recap Bước 7: `{n_active_for_this_command}` và `{n_total_active}`.
|
|
348
|
+
|
|
342
349
|
Nếu file không tồn tại → bỏ qua âm thầm (chưa có lesson nào được ghi nhận).
|
|
343
350
|
|
|
351
|
+
> **Vì sao lọc ở ĐÂY chứ không phải lúc dùng (GAPS-v3 G46):** bản cũ viết *"đọc và lưu **TẤT CẢ**
|
|
352
|
+
> lesson"* ở dòng trên, rồi *"đối chiếu với mọi lesson có `category` khớp"* ở dòng dưới. Bộ lọc
|
|
353
|
+
> **đã tồn tại** — chỉ là chạy **sau** khi đã nạp hết. Có 6 category, nên `/generate-prd` đang nạp
|
|
354
|
+
> cả đống lesson `code-gen` mà nó không bao giờ dùng tới. Chuyển bộ lọc lên trước là thay đổi thứ
|
|
355
|
+
> tự, không phải thêm logic.
|
|
356
|
+
>
|
|
357
|
+
> **Và vì sao chỉ nạp `active`:** trước G46 file lessons **chỉ có đường vào**. Một lesson viết năm
|
|
358
|
+
> ngoái cho code đã bị xoá vẫn được nạp làm ràng buộc cứng, mãi mãi. Cùng lớp lỗi với G28 — giữ
|
|
359
|
+
> một tín hiệu đã hết đúng. Đường ra: `/learn --review` (xem `capture-lesson.md` §Retire).
|
|
360
|
+
>
|
|
361
|
+
> ⚠️ **Không bao giờ tự bỏ lesson vì file quá dài.** Nạp thiếu một guardrail trong im lặng đúng là
|
|
362
|
+
> thứ framework này tồn tại để chống. Vượt ngưỡng thì **cảnh báo** ở recap, người quyết retire.
|
|
363
|
+
|
|
344
364
|
---
|
|
345
365
|
|
|
346
366
|
## Bước 7 — [RECAP] Working Memory Recap (chống lost-in-middle)
|
|
@@ -360,7 +380,8 @@ CLAUDE.md : {root + {service_root} | chỉ {service_root} | chỉ root | ⚠️
|
|
|
360
380
|
Ticket : {ticket_prefix}-
|
|
361
381
|
Dict : {loaded — N canonical terms, M banned terms | missing}
|
|
362
382
|
Entities : {loaded — EntityA, EntityB, EntityC | missing}
|
|
363
|
-
Lessons : {loaded —
|
|
383
|
+
Lessons : {loaded — {n} active cho lệnh này ({tổng} tổng) | chưa có}
|
|
384
|
+
{⚠️ CHỈ IN khi tổng ≥ 40: "{tổng} guardrail đang hoạt động — /learn --review để rà"}
|
|
364
385
|
Platform : {active_platform: system | web | app | — nếu chưa xác định}
|
|
365
386
|
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
|
|
366
387
|
Svc Root : {service_root} — đã nạp conventions + trace_dir từ config service | —
|