openmoneta-dev-kit 1.13.0 → 2.0.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/VERSION CHANGED
@@ -1 +1 @@
1
- 1.13.0
1
+ 2.0.0
@@ -101,6 +101,8 @@ INDEX_FILE="$WORKSPACE/docs/INDEX.md"
101
101
  [[ -f "$INDEX_FILE" ]] || allow
102
102
 
103
103
  MARKER="$WORKSPACE/.cursor/.docs-index-read"
104
+ DECISIONS_INDEX="$WORKSPACE/docs/decisions/INDEX.md"
105
+ DECISIONS_MARKER="$WORKSPACE/.cursor/.decisions-read"
104
106
 
105
107
  case "$PATHS" in
106
108
  *"docs/INDEX.md"*)
@@ -110,6 +112,15 @@ case "$PATHS" in
110
112
  ;;
111
113
  esac
112
114
 
115
+ # Đọc bất kỳ file nào trong docs/decisions/ → set marker decisions-read (vẫn allow vì là docs).
116
+ case "$PATHS" in
117
+ *"docs/decisions/"*)
118
+ mkdir -p "$WORKSPACE/.cursor"
119
+ touch "$DECISIONS_MARKER"
120
+ allow
121
+ ;;
122
+ esac
123
+
113
124
  case "$PATHS" in
114
125
  *"docs/"*|*"plans/"*|*".cursor/"*|*".github/"*|*".vscode/"*) allow ;;
115
126
  *"AGENTS.md"*|*"README"*|*"CHANGELOG"*|*"LICENSE"*) allow ;;
@@ -164,6 +175,29 @@ LƯU Ý:
164
175
 
165
176
  (Hook này chỉ chạy 1 lần / session. Sau khi đọc docs/INDEX.md, marker .cursor/.docs-index-read được set và hook sẽ allow mọi tool call source-code khác trong session.)"
166
177
  fi
178
+
179
+ # === Decisions-read gate (Decision Memory / chống flip-flop kiến trúc) ===
180
+ # Chỉ enforce khi project CÓ docs/decisions/INDEX.md (project chưa có quyết định nào thì bỏ qua → giữ Lean).
181
+ if [[ -f "$DECISIONS_INDEX" && ! -f "$DECISIONS_MARKER" ]]; then
182
+ deny "Bạn đang $TOOL_NAME source code TRƯỚC khi đọc docs/decisions/ (trí nhớ quyết định kiến trúc). Vi phạm Bước 1 (skill requirement-analysis 1.3b).
183
+
184
+ DEBUG:
185
+ - Tool: $TOOL_NAME
186
+ - docs/decisions/INDEX.md: $DECISIONS_INDEX
187
+ - Marker expected: $DECISIONS_MARKER
188
+ - Tool input paths/patterns: $PATHS
189
+
190
+ VÌ SAO ENFORCE:
191
+ - Project này có quyết định kiến trúc đã ghi (ADR). Sửa code mà không đọc lịch sử quyết định = nguy cơ flip-flop kiến trúc (A1 → B1 → A1) làm loạn code.
192
+ - ADR ghi: kiến trúc đang chọn, vì sao, phương án nào đã bị loại + root cause, hạn chế đã biết.
193
+
194
+ HÀNH ĐỘNG NGAY:
195
+ → Read $DECISIONS_INDEX (set marker), tìm ADR liên quan tới module bạn sắp đụng, đọc ADR đó nếu định đổi kiến trúc, sau đó retry $TOOL_NAME.
196
+
197
+ LƯU Ý:
198
+ - Muốn ĐỔI/ĐẢO kiến trúc đã Accepted → phải supersede tường minh (skill decision-recorder), KHÔNG revert lặng lẽ.
199
+ - Marker chỉ cần set 1 lần/session (auto-clean ở sessionStart)."
200
+ fi
167
201
  fi
168
202
 
169
203
  allow
@@ -27,11 +27,12 @@ register_project_cursor() {
27
27
  echo "$abs" >> "$reg" 2>/dev/null || true
28
28
  }
29
29
 
30
- # Reset docs-index marker mỗi session (force re-read INDEX.md)
30
+ # Reset docs-index + decisions marker mỗi session (force re-read INDEX.md + decisions/)
31
31
  if command -v jq >/dev/null 2>&1; then
32
32
  WORKSPACE_FROM_INPUT=$(echo "$INPUT_JSON" | jq -r '.workspace_roots[0] // ""' 2>/dev/null || echo "")
33
33
  if [[ -n "$WORKSPACE_FROM_INPUT" && -d "$WORKSPACE_FROM_INPUT/.cursor" ]]; then
34
34
  rm -f "$WORKSPACE_FROM_INPUT/.cursor/.docs-index-read" 2>/dev/null || true
35
+ rm -f "$WORKSPACE_FROM_INPUT/.cursor/.decisions-read" 2>/dev/null || true
35
36
  fi
36
37
  register_project_cursor "$WORKSPACE_FROM_INPUT" 2>/dev/null || true
37
38
  fi
@@ -50,7 +51,7 @@ SUMMARY='# OpenMoneta Dev Kit v1.7.0 — Quy trình 6 bước (Adaptive Planning
50
51
  6. Safe Push: khi user yêu cầu push, PHẢI sync remote ngay trước push; KHÔNG force-push shared branch.
51
52
 
52
53
  ## 6 bước
53
- - **B1 Phân tích** (skill `requirement-analysis`): Read INDEX → match Token Routing → đọc README module candidate → đọc source → đặt câu hỏi critical để làm rõ yêu cầu, phân tích mọi trường hợp, phản biện và đề xuất phương án tối ưu nếu có (hoặc khai báo skip lý do).
54
+ - **B1 Phân tích** (skill `requirement-analysis`): Read INDEX → match Token Routing → đọc README module candidate → đọc source → đặt câu hỏi critical để làm rõ yêu cầu, phân tích mọi trường hợp, phản biện và đề xuất phương án tối ưu nếu có (hoặc khai báo skip lý do). Nếu có `docs/decisions/INDEX.md`: PHẢI đọc ADR liên quan trước khi đổi kiến trúc (chống flip-flop A1→B1→A1); muốn đảo kiến trúc đã chốt → supersede tường minh (skill `decision-recorder`).
54
55
  - **B2 Thiết kế** (skill `module-architect`): SRP 1 module 1 trách nhiệm. Feature mới có concept riêng → module mới (KHÔNG nhét utils/common/shared). Existing project: backfill `docs/modules/<slug>/README.md` 3 sections nếu thiếu. Module mới → thêm keyword vào Token Routing.
55
56
  - **B3 Adaptive Plan** (skill `plan-writer`): task nhỏ/rõ → không cần plan. Task lớn/rủi ro/mơ hồ → tạo repo plan `Status: Draft`, trình user review, chỉ code sau khi user approve và đổi `Status: In Progress`.
56
57
  - **B4 Triển khai**: nếu có plan active → chỉ edit file trong scope plan. Không plan → chỉ sửa đúng yêu cầu; nếu vượt ngưỡng/rủi ro → dừng tạo plan Draft.
@@ -7,6 +7,8 @@
7
7
  # 7. Cross-module sprawl >3 module → WARN
8
8
  # 8. Token-aware reading marker tồn tại → WARN nếu thiếu
9
9
  # 9. Nếu có plan: section "## Hiểu yêu cầu" có nội dung không trống → BLOCK (audit clarify)
10
+ # 10. ADR supersede integrity: ADR mới "Supersedes: X" thì ADR X phải "Superseded by" → BLOCK
11
+ # 11. Plan khai báo "## Quyết định kiến trúc" (đổi/đảo) nhưng không sinh/sửa ADR → BLOCK
10
12
  #
11
13
  # Đã bỏ (so với v1.4.x):
12
14
  # - Check 2 (test result file) — test giờ on-demand
@@ -134,6 +136,39 @@ if [[ -f "$WORKSPACE/docs/INDEX.md" && ! -f "$WORKSPACE/.cursor/.docs-index-read
134
136
  WARNINGS+=("⚠️ Session có code change nhưng marker .cursor/.docs-index-read không tồn tại — AI có thể đã skip Bước 1 (Read docs/INDEX.md). Verify hook 'enforce-docs-first' đang chạy: cat ~/.cursor/hooks.json | jq '.hooks.preToolUse'. Nếu hook không chạy → restart Cursor sau khi cài v1.5.0.")
135
137
  fi
136
138
 
139
+ # === Check 10/11: Decision Memory (ADR) integrity — chống flip-flop kiến trúc ===
140
+ CHANGED_DECISIONS=$(jq -r '.changes[] | .path' "$CHANGES_FILE" 2>/dev/null | grep -E '^docs/decisions/.*\.md$' | grep -v 'docs/decisions/INDEX.md' || true)
141
+
142
+ # Check 10: ADR mới/sửa khai báo "Supersedes: ADR-NNNN" thì ADR đích phải đã đổi sang "Superseded by"
143
+ if [[ -n "$CHANGED_DECISIONS" ]]; then
144
+ while IFS= read -r rel_adr; do
145
+ [[ -z "$rel_adr" ]] && continue
146
+ ADR_FILE="$WORKSPACE/$rel_adr"
147
+ [[ -f "$ADR_FILE" ]] || continue
148
+ SUPERSEDES=$(grep -ioE 'supersedes\*{0,2}:?[[:space:]]*ADR-[0-9]+' "$ADR_FILE" 2>/dev/null | grep -oE 'ADR-[0-9]+' | head -1 || true)
149
+ if [[ -n "$SUPERSEDES" ]]; then
150
+ NUM=$(echo "$SUPERSEDES" | grep -oE '[0-9]+')
151
+ TARGET=$(ls "$WORKSPACE/docs/decisions/${NUM}-"*.md 2>/dev/null | head -1 || true)
152
+ if [[ -z "$TARGET" ]]; then
153
+ ISSUES+=("❌ ADR '$(basename "$ADR_FILE")' khai báo 'Supersedes: $SUPERSEDES' nhưng không tìm thấy docs/decisions/${NUM}-*.md.")
154
+ elif ! grep -qiE 'superseded by' "$TARGET" 2>/dev/null; then
155
+ ISSUES+=("❌ ADR cũ '$(basename "$TARGET")' bị supersede bởi '$(basename "$ADR_FILE")' nhưng chưa đổi Status sang 'Superseded by ADR-...'. Cập nhật để tránh 2 ADR Accepted xung đột (gây flip-flop). Xem skill decision-recorder.")
156
+ fi
157
+ fi
158
+ done <<< "$CHANGED_DECISIONS"
159
+ fi
160
+
161
+ # Check 11: plan trong session khai báo đổi/đảo kiến trúc nhưng KHÔNG sinh/sửa ADR
162
+ for plan in $ALL_RECENT_PLANS; do
163
+ [[ -z "$plan" || ! -f "$plan" ]] && continue
164
+ if grep -qE "^##[[:space:]]+Quyết định kiến trúc" "$plan" 2>/dev/null; then
165
+ DECL=$(awk '/^##[[:space:]]+Quyết định kiến trúc/{flag=1; next} flag && /^##[[:space:]]/{exit} flag {print}' "$plan" | grep -iE 'supersede|đổi kiến trúc|revert|đảo ngược' || true)
166
+ if [[ -n "$DECL" && -z "$CHANGED_DECISIONS" ]]; then
167
+ ISSUES+=("❌ Plan '$(basename "$plan")' có '## Quyết định kiến trúc' khai báo đổi/đảo kiến trúc nhưng KHÔNG có docs/decisions/*.md được tạo/cập nhật. Tạo/supersede ADR (skill decision-recorder) trước khi đóng session.")
168
+ fi
169
+ fi
170
+ done
171
+
137
172
  # === Output ===
138
173
  WARN_STR=""
139
174
  if [[ ${#WARNINGS[@]} -gt 0 ]]; then
@@ -13,13 +13,14 @@
13
13
 
14
14
  ## Quy trình 6 bước
15
15
 
16
- - **B1 Phân tích** (skill `requirement-analysis`): Read `docs/INDEX.md` → match Token Routing → đọc README module liên quan → đọc source → hỏi clarify nếu yêu cầu chưa rõ (ghi vào `## Hiểu yêu cầu` của plan).
16
+ - **B1 Phân tích** (skill `requirement-analysis`): Read `docs/INDEX.md` → match Token Routing → đọc README module liên quan → đọc source → hỏi clarify nếu yêu cầu chưa rõ (ghi vào `## Hiểu yêu cầu` của plan). **Nếu có `docs/decisions/INDEX.md`: PHẢI đọc nó + ADR liên quan trước khi đổi kiến trúc** (plugin guard chặn read source nếu chưa đọc `docs/decisions/`). Muốn đảo kiến trúc đã chốt → supersede tường minh (skill `decision-recorder`), chống flip-flop A1→B1→A1.
17
17
  - **B2 Thiết kế** (skill `module-architect`): SRP 1 module 1 trách nhiệm. Feature có concept riêng → module mới (KHÔNG nhét utils/common/shared). **Module có source nhưng chưa có doc → BẮT BUỘC backfill `docs/modules/<slug>/README.md` 3 sections (Trách nhiệm + Public API + Dependencies)** trước khi sang B4. Module mới → thêm keyword vào Token Routing của `docs/INDEX.md`.
18
- - **B3 Adaptive Plan** (skill `plan-writer`): task nhỏ/rõ → không cần plan. Task lớn/rủi ro/mơ hồ → tạo repo plan `Status: Draft`, trình user review, chỉ code sau khi user approve và đổi `Status: In Progress`.
18
+ - **B3 Adaptive Plan** (skill `plan-writer`): task nhỏ/rõ → không cần plan. Task lớn/rủi ro/mơ hồ → tạo repo plan `Status: Draft`, trình user review, chỉ code sau khi user approve và đổi `Status: In Progress`. Plan đổi/đảo kiến trúc → thêm section `## Quyết định kiến trúc` (`Supersede ADR-NNNN` + lý do root cause cũ hết đúng).
19
19
  - **B4 Triển khai**: có plan active → chỉ edit file trong scope plan; không plan → chỉ sửa đúng yêu cầu. Mỗi sub-task xong → tick `- [x]`.
20
20
  - **B5 Update doc + close plan** — **mandatory closing checklist** (plugin guard `event:session.idle` sẽ re-prompt nếu thiếu):
21
21
  - [ ] Sync `docs/modules/<slug>/README.md` cho mọi module bị sửa: **Trách nhiệm + Public API + Dependencies**.
22
22
  - [ ] Module mới đã có trong bảng "Modules hiện có" + "Token Routing" của `docs/INDEX.md`.
23
+ - [ ] Nếu session đổi kiến trúc → tạo/supersede ADR trong `docs/decisions/` + link vào README module + cập nhật `docs/decisions/INDEX.md` (skill `decision-recorder`).
23
24
  - [ ] Plan → `Status: Done` + tick mọi checkbox.
24
25
  - [ ] **AUTO-ARCHIVE**: `git mv plans/<file>.md plans/archive/<file>.md` + update `plans/INDEX.md` (Active → Archived).
25
26
  - **B6 Pre-push Sync + Safe Push** (skill `safe-push`, CONDITIONAL): chỉ khi user yêu cầu push. `git fetch` + rebase trước push, conflict logic → hỏi user, `git push` thường, KHÔNG `--force` shared branch.
@@ -31,7 +32,7 @@
31
32
  - OpenMoneta skills được cài vào `~/.config/opencode/skills/*/SKILL.md` và agent nên load bằng skill tool khi cần.
32
33
  - **Bảo trì doc dự án cũ** (ON-DEMAND): khi user yêu cầu tạo/refresh docs còn thiếu hoặc lỗi thời, chạy `bash ~/.config/opencode/scripts/docs-audit.sh` để soi rồi load skill `docs-maintenance` (backfill MISSING + refresh STALE + sync `docs/INDEX.md`).
33
34
  - OpenMoneta subagents được cài vào `~/.config/opencode/agents/`.
34
- - OpenCode plugin guard trong `~/.config/opencode/plugins/openmoneta-guard.ts` enforce workflow tương đương Cursor hooks: `tool.execute.before` (docs-first + plan gate), `tool.execute.after` (track changes), và `event:session.idle` (verify B2/B5: module README + close plan, re-prompt nhắc hoàn tất với loop guard ≤4 lần).
35
+ - OpenCode plugin guard trong `~/.config/opencode/plugins/openmoneta-guard.ts` enforce workflow tương đương Cursor hooks: `tool.execute.before` (docs-first + decisions-read gate + plan gate), `tool.execute.after` (track changes), và `event:session.idle` (verify B2/B5 + ADR integrity: module README, close plan, supersede ADR, re-prompt nhắc hoàn tất với loop guard ≤4 lần).
35
36
 
36
37
  ## Khi Bắt Đầu Task
37
38
 
@@ -375,6 +375,54 @@ function buildVerifyIssues(root: string): string[] {
375
375
  }
376
376
  }
377
377
 
378
+ // Check 10/11: Decision Memory (ADR) integrity — chống flip-flop kiến trúc
379
+ const changedDecisions = paths.filter(
380
+ (p) => /^docs\/decisions\/.*\.md$/.test(p) && p !== "docs/decisions/INDEX.md",
381
+ )
382
+
383
+ // Check 10: ADR mới "Supersedes: ADR-NNNN" thì ADR đích phải đã "Superseded by"
384
+ for (const rel of changedDecisions) {
385
+ const abs = path.join(root, rel)
386
+ if (!fs.existsSync(abs)) continue
387
+ const match = fs.readFileSync(abs, "utf8").match(/supersedes\*{0,2}:?\s*ADR-(\d+)/i)
388
+ if (!match) continue
389
+ const num = match[1]
390
+ const decisionsDir = path.join(root, "docs", "decisions")
391
+ const target = fs.existsSync(decisionsDir)
392
+ ? fs.readdirSync(decisionsDir).find((f) => f.startsWith(`${num}-`) && f.endsWith(".md"))
393
+ : undefined
394
+ if (!target) {
395
+ issues.push(
396
+ `❌ ADR '${path.basename(abs)}' khai báo 'Supersedes: ADR-${num}' nhưng không tìm thấy docs/decisions/${num}-*.md.`,
397
+ )
398
+ } else if (!/superseded by/i.test(fs.readFileSync(path.join(decisionsDir, target), "utf8"))) {
399
+ issues.push(
400
+ `❌ ADR cũ '${target}' bị supersede bởi '${path.basename(abs)}' nhưng chưa đổi Status sang 'Superseded by ADR-...'. Cập nhật để tránh 2 ADR Accepted xung đột (flip-flop). Xem skill decision-recorder.`,
401
+ )
402
+ }
403
+ }
404
+
405
+ // Check 11: plan khai báo "## Quyết định kiến trúc" (đổi/đảo) nhưng không sinh/sửa ADR
406
+ for (const plan of planSet) {
407
+ if (!fs.existsSync(plan)) continue
408
+ const lines = fs.readFileSync(plan, "utf8").split(/\r?\n/)
409
+ let inSection = false
410
+ let declared = false
411
+ for (const line of lines) {
412
+ if (/^##\s+Quyết định kiến trúc/.test(line)) {
413
+ inSection = true
414
+ continue
415
+ }
416
+ if (inSection && /^##\s+/.test(line)) break
417
+ if (inSection && /supersede|đổi kiến trúc|revert|đảo ngược/i.test(line)) declared = true
418
+ }
419
+ if (declared && changedDecisions.length === 0) {
420
+ issues.push(
421
+ `❌ Plan '${path.basename(plan)}' có '## Quyết định kiến trúc' khai báo đổi/đảo kiến trúc nhưng KHÔNG có docs/decisions/*.md được tạo/cập nhật. Tạo/supersede ADR (skill decision-recorder) trước khi kết thúc.`,
422
+ )
423
+ }
424
+ }
425
+
378
426
  return issues
379
427
  }
380
428
 
@@ -450,8 +498,10 @@ export const OpenMonetaGuard = async (ctx: GuardContext) => {
450
498
  const client = ctx.client
451
499
  const root = projectRoot(ctx)
452
500
  const marker = path.join(root, ".cursor", ".docs-index-read")
501
+ const decisionsMarker = path.join(root, ".cursor", ".decisions-read")
453
502
  const guardLoadedMarker = path.join(root, ".cursor", ".openmoneta-guard-loaded.json")
454
503
  const docsIndex = path.join(root, "docs", "INDEX.md")
504
+ const decisionsIndex = path.join(root, "docs", "decisions", "INDEX.md")
455
505
  const pendingEdits = new Map<string, string[]>()
456
506
 
457
507
  fs.mkdirSync(path.dirname(marker), { recursive: true })
@@ -461,7 +511,7 @@ export const OpenMonetaGuard = async (ctx: GuardContext) => {
461
511
  {
462
512
  loaded_at: new Date().toISOString(),
463
513
  root,
464
- version: "1.13.0",
514
+ version: "2.0.0",
465
515
  load_count: globalState[globalKey],
466
516
  },
467
517
  null,
@@ -473,10 +523,13 @@ export const OpenMonetaGuard = async (ctx: GuardContext) => {
473
523
  registerProjectOpenCode(root)
474
524
 
475
525
  // OpenCode does not have Cursor's sessionStart hook, so reset the docs-first
476
- // marker when the plugin is loaded for a new OpenCode process/session.
526
+ // + decisions-read markers when the plugin is loaded for a new OpenCode process/session.
477
527
  if (fs.existsSync(marker)) {
478
528
  fs.rmSync(marker, { force: true })
479
529
  }
530
+ if (fs.existsSync(decisionsMarker)) {
531
+ fs.rmSync(decisionsMarker, { force: true })
532
+ }
480
533
 
481
534
  return {
482
535
  "tool.execute.before": async (
@@ -493,6 +546,11 @@ export const OpenMonetaGuard = async (ctx: GuardContext) => {
493
546
  fs.writeFileSync(marker, new Date().toISOString())
494
547
  return
495
548
  }
549
+ if (target.includes("docs/decisions/")) {
550
+ fs.mkdirSync(path.dirname(decisionsMarker), { recursive: true })
551
+ fs.writeFileSync(decisionsMarker, new Date().toISOString())
552
+ return
553
+ }
496
554
  if (shouldCheckRead(tool, target) && !fs.existsSync(marker)) {
497
555
  fail(
498
556
  [
@@ -505,6 +563,20 @@ export const OpenMonetaGuard = async (ctx: GuardContext) => {
505
563
  ].join("\n"),
506
564
  )
507
565
  }
566
+ if (shouldCheckRead(tool, target) && fs.existsSync(decisionsIndex) && !fs.existsSync(decisionsMarker)) {
567
+ fail(
568
+ [
569
+ "Bạn đang đọc/search source code trước khi đọc `docs/decisions/` (trí nhớ quyết định kiến trúc).",
570
+ "",
571
+ "Project này có ADR. Sửa code mà không đọc lịch sử quyết định = nguy cơ flip-flop kiến trúc (A1 → B1 → A1).",
572
+ "",
573
+ "Quy trình đúng:",
574
+ "1. Read `docs/decisions/INDEX.md`.",
575
+ "2. Đọc ADR liên quan tới module nếu định đổi kiến trúc.",
576
+ "3. Muốn đảo kiến trúc đã chốt → supersede tường minh (skill decision-recorder).",
577
+ ].join("\n"),
578
+ )
579
+ }
508
580
  }
509
581
 
510
582
  if (isBashTool(tool) && fs.existsSync(docsIndex)) {
@@ -514,6 +586,11 @@ export const OpenMonetaGuard = async (ctx: GuardContext) => {
514
586
  fs.writeFileSync(marker, new Date().toISOString())
515
587
  return
516
588
  }
589
+ if (command.includes("docs/decisions")) {
590
+ fs.mkdirSync(path.dirname(decisionsMarker), { recursive: true })
591
+ fs.writeFileSync(decisionsMarker, new Date().toISOString())
592
+ return
593
+ }
517
594
  if (isSourceSearchCommand(command) && !fs.existsSync(marker)) {
518
595
  fail(
519
596
  [
@@ -528,6 +605,21 @@ export const OpenMonetaGuard = async (ctx: GuardContext) => {
528
605
  ].join("\n"),
529
606
  )
530
607
  }
608
+ if (
609
+ isSourceSearchCommand(command) &&
610
+ fs.existsSync(decisionsIndex) &&
611
+ !fs.existsSync(decisionsMarker)
612
+ ) {
613
+ fail(
614
+ [
615
+ "Bạn đang chạy shell command search source code trước khi đọc `docs/decisions/` (trí nhớ quyết định kiến trúc).",
616
+ "",
617
+ `Command bị chặn: ${command}`,
618
+ "",
619
+ "Read `docs/decisions/INDEX.md` trước (chống flip-flop kiến trúc), rồi mới search source.",
620
+ ].join("\n"),
621
+ )
622
+ }
531
623
  }
532
624
 
533
625
  if (isEditTool(tool)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmoneta-dev-kit",
3
- "version": "1.13.0",
3
+ "version": "2.0.0",
4
4
  "description": "OpenMoneta Dev Kit — Biến Cursor IDE / OpenCode thành team developer hoàn chỉnh với quy trình 6 bước, adaptive planning, hooks enforcement, và token-aware doc routing",
5
5
  "keywords": [
6
6
  "cursor",
@@ -337,6 +337,40 @@ sync_plans_index() {
337
337
  UPDATED+=("sync: $dst (giữ ACTIVE PLANS / PLANS EXTRA / ARCHIVED PLANS / PLANS NOTES)")
338
338
  }
339
339
 
340
+ sync_decisions_index() {
341
+ local src="$TEMPLATES/decisions-INDEX.md.tpl"
342
+ local dst="docs/decisions/INDEX.md"
343
+
344
+ # Template mới (v2.0.0+). Nếu install home cũ chưa có → bỏ qua êm.
345
+ [[ -f "$src" ]] || return 0
346
+
347
+ if [[ ! -f "$dst" ]]; then
348
+ render_template "$src" "$dst"
349
+ CREATED+=("file: $dst")
350
+ return
351
+ fi
352
+
353
+ local rendered content
354
+ rendered=$(mktemp)
355
+ render_template "$src" "$rendered"
356
+
357
+ content=$(mktemp)
358
+ if extract_block "$dst" "PROJECT DECISIONS" "$content" || extract_section_table "$dst" "## Quyết định hiện có" "$content"; then
359
+ replace_block "$rendered" "PROJECT DECISIONS" "$content"
360
+ fi
361
+ rm -f "$content"
362
+
363
+ if cmp -s "$rendered" "$dst"; then
364
+ SKIPPED+=("file: $dst (đã sync, không đổi)")
365
+ rm -f "$rendered"
366
+ return
367
+ fi
368
+
369
+ backup_before_sync "$dst"
370
+ mv "$rendered" "$dst"
371
+ UPDATED+=("sync: $dst (giữ PROJECT DECISIONS)")
372
+ }
373
+
340
374
  sync_opencode_project_guard() {
341
375
  # Guard is now global-only (~/.config/opencode/plugins/).
342
376
  # Just clean up any old project-level plugins from pre-v1.9 migration.
@@ -347,6 +381,7 @@ sync_opencode_project_guard() {
347
381
  echo "==> Tạo thư mục tối thiểu..."
348
382
  ensure_dir "docs"
349
383
  ensure_dir "docs/modules"
384
+ ensure_dir "docs/decisions"
350
385
  ensure_dir "plans"
351
386
  ensure_dir "plans/archive"
352
387
  ensure_dir ".cursor"
@@ -356,6 +391,7 @@ echo "==> Sync managed templates..."
356
391
 
357
392
  sync_agents_md
358
393
  sync_docs_index
394
+ sync_decisions_index
359
395
  sync_plans_index
360
396
  sync_opencode_project_guard
361
397
 
@@ -365,7 +401,9 @@ GITIGNORE=".gitignore"
365
401
  PATTERNS=(
366
402
  ".cursor/.session-changes.json"
367
403
  ".cursor/.docs-index-read"
404
+ ".cursor/.decisions-read"
368
405
  ".cursor/.openmoneta-guard-loaded.json"
406
+ ".cursor/.openmoneta-verify-loop.json"
369
407
  ".cursor/.changelog-baseline"
370
408
  ".cursor/.last-test-result"
371
409
  ".cursor/.ui-session.json"
@@ -0,0 +1,61 @@
1
+ ---
2
+ name: decision-recorder
3
+ description: "Dùng khi ra/đổi/đảo một quyết định KIẾN TRÚC (chọn giữa ≥2 phương án, đổi cách triển khai một tính năng, hoặc muốn revert kiến trúc đã có). Ghi ADR vào docs/decisions/ để chống flip-flop kiến trúc xuyên session. KHÔNG dùng cho task code thường."
4
+ ---
5
+
6
+ # Decision Recorder (ADR)
7
+
8
+ Lớp **Decision Memory** giải quyết vòng lặp phá code: `A1 lỗi → session đổi sang B1 → B1 lỗi nhỏ → session mới (quên hết) đưa về A1 → lỗi cũ tái diễn`. ADR lưu **WHY + WHY-NOT + root cause** để mọi session sau hiểu vì sao kiến trúc hiện tại được chọn.
9
+
10
+ ## Iron Law
11
+
12
+ ```
13
+ KHÔNG ĐẢO NGƯỢC một kiến trúc đã Accepted (LOCKED) khi CHƯA:
14
+ (1) đọc ADR đang khóa của nó, VÀ
15
+ (2) supersede tường minh (ADR mới + chứng minh root cause cũ KHÔNG còn đúng).
16
+ ```
17
+
18
+ **Vi phạm chữ = vi phạm tinh thần.** Đổi kiến trúc "cho nhanh" rồi định ghi ADR sau = sai. Phải đọc + supersede TRƯỚC.
19
+
20
+ ## Khi nào TẠO ADR
21
+
22
+ - Chọn giữa ≥2 phương án kiến trúc có trade-off thật (state management, data flow, sync vs async, pattern...).
23
+ - Đổi cách triển khai một tính năng đang chạy (vd: polling → websocket).
24
+ - `systematic-debugging` xác nhận một approach hỏng do bản chất kiến trúc (không phải bug vặt).
25
+ - Đảo ngược / thay thế một quyết định cũ.
26
+
27
+ ## Khi nào KHÔNG tạo ADR (tránh token bloat)
28
+
29
+ - Bug fix, thêm field, đổi text/CSS, rename, refactor nội bộ không đổi kiến trúc.
30
+ - Task nhỏ thường ngày. ADR chỉ dành cho **quyết định kiến trúc đáng nhớ**.
31
+
32
+ ## Cách tạo ADR mới
33
+
34
+ 1. Đọc `docs/decisions/INDEX.md` để biết số ADR lớn nhất hiện có → `NNNN` mới = max + 1 (4 chữ số).
35
+ 2. Copy template `~/.cursor/templates/adr.md.tpl` (hoặc `~/.config/opencode/templates/adr.md.tpl`) → `docs/decisions/NNNN-<slug>.md`.
36
+ 3. Điền 5 section: Context, Decision, **Rejected alternatives (kèm root cause)**, Consequences (known limitations), Failed approaches log.
37
+ 4. Điền field: `Status: Accepted (LOCKED)`, `Date`, `Module(s)`, `Plan nguồn` (link plan archive nếu có — đây là **tầng chi tiết**), `Supersedes` (nếu thay ADR cũ).
38
+ 5. Cập nhật bảng trong `docs/decisions/INDEX.md`.
39
+ 6. Link ADR vào `docs/modules/<slug>/README.md` section "## Quyết định kiến trúc (ADR)".
40
+
41
+ ## Quy trình SUPERSEDE (đảo/đổi quyết định cũ) — BẮT BUỘC
42
+
43
+ 1. Đọc ADR cũ (`Accepted (LOCKED)`) + phần **Rejected alternatives** + **Failed approaches log** của nó.
44
+ 2. Tự hỏi: root cause khiến phương án cũ bị loại có còn đúng không? Nếu CÒN đúng → **KHÔNG revert** (đây chính là cái bẫy flip-flop).
45
+ 3. Nếu thật sự cần đổi: tạo ADR mới với `Supersedes: ADR-XXXX` + chứng minh trong section Context vì sao root cause cũ hết hiệu lực.
46
+ 4. Đổi ADR cũ sang `Status: Superseded by ADR-NNNN` (giữ nguyên nội dung — không xóa, là lịch sử).
47
+ 5. Plan tương ứng phải có section "## Quyết định kiến trúc" ghi rõ supersede (xem skill `plan-writer`).
48
+
49
+ ## Token-lean (guideline, KHÔNG enforce cứng)
50
+
51
+ - Soft target **~40 dòng/ADR**, viết gạch đầu dòng. Quy tắc vàng: **"viết đủ để session sau không lặp lại sai lầm, không hơn"**.
52
+ - **Miễn giới hạn** cho phần root cause chi tiết + "Failed approaches log" (giữ sắc thái quan trọng).
53
+ - Cấu trúc 2 tầng: ADR = tóm tắt (luôn nạp); `Plan nguồn` (`plans/archive/<file>.md`) = chi tiết (nạp khi cần). KHÔNG tạo file `*-detail.md` riêng.
54
+
55
+ ## Red Flags — DỪNG
56
+
57
+ - Định đổi kiến trúc mà chưa đọc ADR đang khóa của module.
58
+ - Định revert về phương án từng bị loại mà không kiểm tra root cause cũ.
59
+ - Tạo ADR mới `Accepted` nhưng KHÔNG đổi ADR cũ sang `Superseded by` (để 2 ADR Accepted xung đột — hook `verify-completion` Check 10 sẽ chặn).
60
+ - ADR phình dài vì văn xuôi thừa (không phải vì root cause).
61
+ - Tạo ADR cho task code thường (lạm dụng → token bloat).
@@ -228,9 +228,18 @@ Quy tắc maintenance:
228
228
  - Có `import` mới từ module khác? → thêm.
229
229
  - Có `import` bị xóa? → xóa.
230
230
  5. Nếu module **mới được backfill** ở Bước 2.2 → đảm bảo `docs/INDEX.md` đã có trong bảng "Modules hiện có" + Token Routing.
231
+ 6. **Nếu session ra/đổi quyết định kiến trúc cho module này** → tạo/cập nhật ADR (skill `decision-recorder`) trong `docs/decisions/` + thêm/sync section "## Quyết định kiến trúc (ADR)" trong README module (list các ADR-NNNN liên quan) + cập nhật `docs/decisions/INDEX.md`. KHÔNG bắt buộc cho task không đổi kiến trúc.
231
232
 
232
233
  > KHÔNG cần update `changelog.md` cấp module — v1.5.0 bỏ enforcement này. Lịch sử dùng `git log` thay.
233
234
 
235
+ **Section ADR trong README module** (chỉ thêm khi module có quyết định kiến trúc):
236
+
237
+ ```markdown
238
+ ## Quyết định kiến trúc (ADR)
239
+
240
+ - [ADR-0003](../../decisions/0003-state-via-store.md) — dùng store tập trung thay vì props drilling (Accepted).
241
+ ```
242
+
234
243
  ### Anti-patterns
235
244
 
236
245
  - ❌ Copy nguyên block code vào README (sẽ stale).
@@ -243,6 +252,7 @@ Quy tắc maintenance:
243
252
 
244
253
  - [ ] Module README đã sync với public API thực tế (nếu API đổi).
245
254
  - [ ] Module mới được tạo → đã thêm vào bảng "Modules hiện có" + Token Routing trong `docs/INDEX.md`.
255
+ - [ ] Nếu session đổi kiến trúc → ADR đã tạo/cập nhật trong `docs/decisions/` + link vào README module + bảng `docs/decisions/INDEX.md` (xem skill `decision-recorder`).
246
256
  - [ ] Plan đánh dấu `Status: Done`.
247
257
  - [ ] Plan file đã `git mv` sang `plans/archive/<file>.md` (auto-archive).
248
258
  - [ ] `plans/INDEX.md` đã chuyển plan từ Active sang Archived.
@@ -91,6 +91,7 @@ Không được tự tạo plan rồi tự approve.
91
91
  - Định sửa file không nằm trong `## Files ảnh hưởng`.
92
92
  - Định bỏ qua plan cho task chạm auth/payment/db/migration/deploy.
93
93
  - Section `## Hiểu yêu cầu` trống mà vẫn định code.
94
+ - Định đổi/đảo kiến trúc đã có ADR mà plan thiếu section `## Quyết định kiến trúc` (supersede).
94
95
 
95
96
  **Tất cả đều nghĩa là: DỪNG, quay lại review gate.**
96
97
 
@@ -118,6 +119,17 @@ HOẶC
118
119
 
119
120
  <1-2 câu: làm gì, vì sao>
120
121
 
122
+ ## Quyết định kiến trúc
123
+
124
+ > CHỈ BẮT BUỘC khi plan ĐỔI/ĐẢO kiến trúc một module (đổi cách triển khai, revert, chọn lại pattern). Bỏ section này cho plan thường. Hook `verify-completion` Check 11 chặn nếu section này khai báo đổi kiến trúc nhưng KHÔNG sinh/sửa ADR trong `docs/decisions/`.
125
+
126
+ - **ADR liên quan**: ADR-NNNN (đọc trước khi đổi).
127
+ - **Supersede**: `Supersede ADR-XXXX` (nếu đảo ngược quyết định cũ).
128
+ - **Lý do root cause cũ không còn đúng**: <chứng minh vì sao phương án cũ từng bị loại nay đã ổn — nếu không chứng minh được thì KHÔNG đổi>.
129
+ - **ADR sẽ tạo/cập nhật ở Bước 5**: `docs/decisions/NNNN-<slug>.md`.
130
+
131
+ Xem skill `decision-recorder` cho quy trình supersede đầy đủ.
132
+
121
133
  ## Modules ảnh hưởng
122
134
 
123
135
  > Hook `verify-completion` Check 6 sẽ chặn nếu module ở đây không có README. Quy ước slug xem skill `module-architect` Bước 2.1.
@@ -38,6 +38,16 @@ description: "Dùng khi bắt đầu phân tích một yêu cầu trước khi t
38
38
  - **KHÔNG scan** `docs/architecture/`, `docs/api/`, `docs/security/` trừ khi yêu cầu user trực tiếp đụng đến.
39
39
  - Sau khi đọc README → mới được Read/Grep/Glob source code của module đó (hook đã unlock).
40
40
 
41
+ ### Bước 1.3b — Đọc trí nhớ quyết định (ADR) — CHỐNG FLIP-FLOP
42
+
43
+ > **HOOK ENFORCEMENT**: nếu `docs/decisions/INDEX.md` tồn tại, hook `enforce-docs-first` chặn Read source cho tới khi đã đọc `docs/decisions/`. Marker: `<workspace>/.cursor/.decisions-read` (auto-clean mỗi sessionStart).
44
+
45
+ - Đọc `docs/decisions/INDEX.md` → tìm ADR liên quan tới module candidate (xem cột Module / link trong README module).
46
+ - **Nếu yêu cầu có dấu hiệu đổi/đảo kiến trúc** (đổi cách triển khai, "chuyển sang...", "thử cách khác", revert) → BẮT BUỘC đọc ADR `Accepted (LOCKED)` của module đó TRƯỚC.
47
+ - Kiểm tra phần **Rejected alternatives** + **Failed approaches log**: phương án bạn định chuyển sang có phải cái đã từng bị loại/hỏng không? Nếu CÓ và root cause còn đúng → KHÔNG lặp lại (đây chính là cái bẫy `A1 → B1 → A1`).
48
+ - Khi ADR tóm tắt chưa đủ ngữ cảnh → theo link **`Plan nguồn`** xuống `plans/archive/<file>.md` (tầng chi tiết, chỉ nạp khi cần).
49
+ - Muốn đổi kiến trúc đã khóa → áp dụng skill `decision-recorder` (quy trình supersede tường minh).
50
+
41
51
  #### Anti-pattern token waste
42
52
 
43
53
  - ❌ Đọc tất cả `docs/modules/*/README.md` "để hiểu toàn bộ dự án" → 19 module × 30 dòng = ~9k tokens phí phạm.
@@ -155,6 +165,7 @@ Subagent trả về tóm tắt + danh sách câu hỏi, parent agent dùng để
155
165
  ## Output bắt buộc trước khi sang Bước 2
156
166
 
157
167
  - [ ] Đã đọc `docs/INDEX.md` (hook unlock cho phép Read source).
168
+ - [ ] Đã đọc `docs/decisions/INDEX.md` + ADR liên quan (nếu tồn tại); nếu định đổi kiến trúc → đã đọc ADR đang khóa + check root cause cũ.
158
169
  - [ ] Tóm tắt yêu cầu (2-3 câu).
159
170
  - [ ] Liệt kê module ảnh hưởng (slug + trạng thái có sẵn / NEW / backfill).
160
171
  - [ ] Giả thuyết / edge cases / rủi ro.
@@ -80,6 +80,15 @@ Sau **3+ fix thất bại**, đây KHÔNG phải giả thuyết sai mà là **ki
80
80
 
81
81
  → **DỪNG, hỏi user** trước khi thử fix thứ 4: pattern này có sai từ gốc không? Có nên đổi kiến trúc thay vì vá tiếp không?
82
82
 
83
+ ### Persist root cause vào ADR (CHỐNG FLIP-FLOP xuyên session)
84
+
85
+ Khi đã **xác nhận** một approach/kiến trúc hỏng do bản chất (không phải bug vặt):
86
+
87
+ 1. Tìm ADR liên quan trong `docs/decisions/` (theo module). Nếu chưa có ADR cho concern đó → tạo qua skill `decision-recorder`.
88
+ 2. **Append vào "Failed approaches log"** của ADR: `YYYY-MM-DD — <approach> — root cause: <nguyên nhân gốc>`. (Phần này miễn giới hạn dòng.)
89
+ 3. Mục đích: session sau đọc ADR sẽ KHÔNG thử lại đúng approach đã hỏng (đây chính là cái bẫy `A1 → B1 → A1`).
90
+ 4. Nếu quyết định **đổi kiến trúc** → theo quy trình supersede của `decision-recorder` (tạo ADR mới + đổi ADR cũ sang Superseded), KHÔNG revert lặng lẽ.
91
+
83
92
  ## Bảng Rationalization (cái cớ → sự thật)
84
93
 
85
94
  | Cái cớ | Sự thật |
@@ -120,3 +129,4 @@ Nếu bắt gặp mình đang nghĩ:
120
129
 
121
130
  - `test-strategy` — viết test reproduce bug ở Phase 4 (khi cần TDD-first).
122
131
  - `plan-writer` — nếu fix biến thành refactor lớn/đổi kiến trúc → dừng, tạo plan.
132
+ - `decision-recorder` — persist root cause của approach hỏng vào ADR + quy trình supersede khi đổi kiến trúc (chống flip-flop).
@@ -21,9 +21,10 @@
21
21
  1. Read `docs/INDEX.md` (Modules + Token Routing) → match keyword user → 1-3 module candidate.
22
22
  2. Read `plans/INDEX.md` (check overlap với plan active).
23
23
  3. Read `docs/modules/<candidate>/README.md` (CHỈ module liên quan, không scan toàn bộ).
24
- 4. Read source code module candidate (giờ hook unlock).
25
- 5. Tóm tắt yêu cầu + giả thuyết + edge case + rủi ro.
26
- 6. **Đặt câu hỏi critical để làm rõ yêu cầu** (`AskQuestion`) HOẶC khai báo `Lý do skip clarify` cho task trivial (typo, bump version, đổi 1 hằng số). Mục đích: làm rõ yêu cầu người dùng, phân tích tất cả trường hợp có thể xảy ra, phản biện và đề xuất phương án tối ưu hơn nếu có. Số lượng câu hỏi tùy thuộc vào độ phức tạp của task.
24
+ 4. **Nếu có `docs/decisions/INDEX.md`**: Read + ADR liên quan của module candidate (hook `enforce-docs-first` chặn Read source nếu chưa đọc `docs/decisions/`). Định đổi kiến trúc → đọc ADR đang khóa + check root cause cũ TRƯỚC (chống flip-flop A1→B1→A1).
25
+ 5. Read source code module candidate (giờ hook unlock).
26
+ 6. Tóm tắt yêu cầu + giả thuyết + edge case + rủi ro.
27
+ 7. **Đặt câu hỏi critical để làm rõ yêu cầu** (`AskQuestion`) HOẶC khai báo `Lý do skip clarify` cho task trivial (typo, bump version, đổi 1 hằng số). Mục đích: làm rõ yêu cầu người dùng, phân tích tất cả trường hợp có thể xảy ra, phản biện và đề xuất phương án tối ưu hơn nếu có. Số lượng câu hỏi tùy thuộc vào độ phức tạp của task.
27
28
 
28
29
  ### Bước 2 — Thiết kế module
29
30
 
@@ -43,6 +44,7 @@
43
44
  - Khi đã có plan `In Progress`, hook `check-plan-exists` enforce file scope theo `## Files ảnh hưởng` / `## Files thay đổi`.
44
45
  - Khi plan còn `Draft`, hook chặn code edit để tránh Agent tự approve plan.
45
46
  - Plan đụng >2 module → BẮT BUỘC thêm subsection "Lý do cross-module".
47
+ - **Plan đổi/đảo kiến trúc** → BẮT BUỘC thêm section "## Quyết định kiến trúc" (`Supersede ADR-NNNN` + lý do root cause cũ hết đúng). Hook `verify-completion` Check 11 chặn nếu khai báo đổi kiến trúc mà không sinh ADR.
46
48
  - Update `plans/INDEX.md` (thêm vào Active).
47
49
 
48
50
  ### Bước 4 — Triển khai
@@ -58,6 +60,7 @@
58
60
 
59
61
  - Sync `docs/modules/<slug>/README.md` (Public API + Dependencies) nếu API đổi.
60
62
  - Module mới → đảm bảo đã có trong bảng "Modules hiện có" + "Token Routing" của `docs/INDEX.md`.
63
+ - **Nếu session ra/đổi quyết định kiến trúc** → tạo/supersede ADR trong `docs/decisions/` + link vào README module (section "## Quyết định kiến trúc (ADR)") + cập nhật `docs/decisions/INDEX.md` (skill `decision-recorder`).
61
64
  - Plan → `Status: Done` + tick mọi checkbox.
62
65
  - **AUTO-ARCHIVE**: `git mv plans/<file>.md plans/archive/<file>.md` + update `plans/INDEX.md`.
63
66
 
@@ -83,7 +86,7 @@
83
86
 
84
87
  ## Skills có sẵn
85
88
 
86
- **Core (luôn relevant)**: `requirement-analysis`, `module-architect`, `plan-writer`.
89
+ **Core (luôn relevant)**: `requirement-analysis`, `module-architect`, `plan-writer`, `decision-recorder`.
87
90
 
88
91
  **Core conditional**: `safe-push` (Bước 6 — chỉ khi user yêu cầu push).
89
92
 
@@ -95,9 +98,9 @@
95
98
 
96
99
  | Hook | Khi nào | Hậu quả |
97
100
  |---|---|---|
98
- | `enforce-docs-first` | preToolUse Read/Glob/Grep | BLOCK source code đọc nếu chưa Read `docs/INDEX.md` |
101
+ | `enforce-docs-first` | preToolUse Read/Glob/Grep | BLOCK source code đọc nếu chưa Read `docs/INDEX.md`; nếu có `docs/decisions/INDEX.md` thì còn BLOCK đến khi đã đọc `docs/decisions/` (chống flip-flop kiến trúc) |
99
102
  | `check-plan-exists` | preToolUse Write/StrReplace/EditNotebook/Delete | ALLOW task thường không plan; BLOCK file nhạy cảm không plan, plan Draft, hoặc file ngoài scope plan |
100
- | `verify-completion` | stop (Cursor) / `session.idle` (OpenCode) | Cursor BLOCK kết thúc nếu Check 5/6/9 fail; OpenCode re-prompt nhắc hoàn tất (plan Done + checkbox, module README, "Hiểu yêu cầu"), loop guard ≤4 lần |
103
+ | `verify-completion` | stop (Cursor) / `session.idle` (OpenCode) | Cursor BLOCK kết thúc nếu Check 5/6/9/10/11 fail (gồm ADR supersede integrity + plan đổi kiến trúc thiếu ADR); OpenCode re-prompt nhắc hoàn tất, loop guard ≤4 lần |
101
104
 
102
105
  ## Override cho dự án này
103
106
 
@@ -0,0 +1,37 @@
1
+ # ADR-{{NNNN}}: {{Tiêu đề quyết định}}
2
+
3
+ <!--
4
+ HƯỚNG DẪN (xóa khi viết xong):
5
+ - Token-lean: viết gạch đầu dòng, SOFT TARGET ~40 dòng. Đây là GUIDELINE, không phải luật cứng.
6
+ - Quy tắc vàng: "viết đủ để session sau KHÔNG lặp lại sai lầm, không hơn".
7
+ - ĐƯỢC PHÉP vượt 40 dòng cho phần root cause chi tiết và "Failed approaches log" (miễn trừ).
8
+ - Cấu trúc 2 tầng: file này = TÓM TẮT (luôn nạp). Chi tiết đầy đủ nằm ở "Plan nguồn" (nạp khi cần).
9
+ - Đánh số {{NNNN}} = max ADR hiện có + 1 (4 chữ số: 0001, 0002, ...).
10
+ -->
11
+
12
+ **Status**: Accepted (LOCKED) <!-- Accepted (LOCKED) | Proposed | Superseded by ADR-MMMM -->
13
+ **Date**: {{YYYY-MM-DD}}
14
+ **Module(s)**: {{slug-module-liên-quan}}
15
+ **Plan nguồn**: {{plans/archive/<file>.md hoặc "—" nếu quyết định inline}}
16
+ **Supersedes**: {{ADR-XXXX hoặc "—"}}
17
+
18
+ ## Context — vấn đề cần quyết
19
+
20
+ - (vì sao phải ra quyết định này)
21
+
22
+ ## Decision — chọn gì (kiến trúc hiện hành)
23
+
24
+ - (kiến trúc/hướng được chọn — đây là cái đang chạy)
25
+
26
+ ## Rejected alternatives — phương án bị loại + VÌ SAO
27
+
28
+ - **{{Phương án A}}**: bị loại vì {{lý do / root cause nếu đã thử & hỏng}}.
29
+ - (giữ sắc thái root cause để session sau không thử lại cái đã hỏng)
30
+
31
+ ## Consequences — hệ quả, known limitations, accepted trade-offs
32
+
33
+ - (những hạn chế đã biết — gồm các "bug nhỏ" chấp nhận được, đừng để session sau tưởng là lỗi cần đổi kiến trúc)
34
+
35
+ ## Failed approaches log — append-only (miễn giới hạn dòng)
36
+
37
+ <!-- systematic-debugging append vào đây khi 1 approach được XÁC NHẬN hỏng. Format: YYYY-MM-DD — approach — root cause. -->
@@ -0,0 +1,28 @@
1
+ <!-- KIT_VERSION: v{{VERSION}} -->
2
+ # Decisions Index (ADR)
3
+
4
+ > **Trí nhớ quyết định kiến trúc của dự án.** AI ở Bước 1 PHẢI đọc file này trước khi sửa source (hook `enforce-docs-first` chặn nếu chưa đọc). Mỗi quyết định kiến trúc = 1 file `docs/decisions/NNNN-<slug>.md`.
5
+ >
6
+ > **Mục đích**: chống flip-flop kiến trúc xuyên session — Agent biết đã chọn gì, vì sao, đã loại phương án nào (kèm root cause), và hạn chế đã biết.
7
+
8
+ ## Cách dùng (BẮT BUỘC)
9
+
10
+ 1. **Trước khi đổi kiến trúc 1 module** → đọc ADR `Accepted (LOCKED)` của module đó. KHÔNG đảo ngược khi chưa supersede tường minh.
11
+ 2. **Tạo ADR mới**: copy `~/.cursor/templates/adr.md.tpl` (hoặc `~/.config/opencode/templates/adr.md.tpl`) → `docs/decisions/NNNN-<slug>.md`, đánh số `NNNN` = max hiện có + 1.
12
+ 3. **Supersede (đảo/đổi quyết định cũ)**: tạo ADR mới với `Supersedes: ADR-XXXX` + chứng minh root cause cũ hết đúng; đổi ADR cũ sang `Status: Superseded by ADR-NNNN`. Cập nhật bảng dưới.
13
+ 4. Cập nhật bảng dưới + link ADR vào `docs/modules/<slug>/README.md` (section "## Quyết định kiến trúc (ADR)").
14
+ 5. Chi tiết: skill `decision-recorder`.
15
+
16
+ ## Quyết định hiện có
17
+
18
+ <!-- BEGIN PROJECT DECISIONS -->
19
+ | ADR | Tiêu đề (ngắn) | Status | Module(s) | Date |
20
+ |---|---|---|---|---|
21
+ | (chưa có) | | | | |
22
+ <!-- END PROJECT DECISIONS -->
23
+
24
+ ## Quy ước
25
+
26
+ - File: `docs/decisions/NNNN-<slug-kebab-case>.md` (NNNN = 4 chữ số tăng dần).
27
+ - Status: `Accepted (LOCKED)` (đang hiệu lực, khóa) → `Superseded by ADR-MMMM` (đã thay thế).
28
+ - ADR token-lean (soft target ~40 dòng); chi tiết đầy đủ ở `Plan nguồn` (tầng chi tiết, nạp khi cần).
@@ -9,6 +9,7 @@
9
9
  | Đường dẫn | Mục đích |
10
10
  |---|---|
11
11
  | `docs/modules/<name>/README.md` | Mục đích, public API, deps của từng module |
12
+ | `docs/decisions/INDEX.md` + `docs/decisions/NNNN-*.md` | Trí nhớ quyết định kiến trúc (ADR): chọn gì, vì sao, loại phương án nào, root cause |
12
13
  <!-- END PROJECT DOC STRUCTURE -->
13
14
 
14
15
  ## Modules hiện có
@@ -34,6 +35,7 @@
34
35
  | email, mail, notification, thông báo, push, SMS | (vd: `notifications`) | |
35
36
  | upload, file, ảnh, image, storage, S3 | (vd: `storage`) | |
36
37
  | search, tìm kiếm, filter, sort | (vd: `search`) | |
38
+ | quyết định, kiến trúc, ADR, decision, architecture, vì sao, why, revert, đảo ngược, supersede | `docs/decisions/` (xem `docs/decisions/INDEX.md`) | Đọc ADR liên quan TRƯỚC khi đổi kiến trúc |
37
39
  | (thêm dòng cho mỗi domain concept của dự án) | | |
38
40
  <!-- END PROJECT TOKEN ROUTING -->
39
41
 
@@ -46,6 +48,11 @@
46
48
  2. Update bảng "Modules hiện có" ở trên.
47
49
  3. **BẮT BUỘC**: thêm 3-5 keyword vào bảng "Token Routing" (cho cả VN + EN).
48
50
  4. Tạo source folder tương ứng (`src/modules/<name>/` hoặc `apps/<name>/` hoặc `packages/<name>/`).
51
+ 5. Nếu module có quyết định kiến trúc đáng nhớ → tạo ADR (`docs/decisions/`) + link vào README module (section "## Quyết định kiến trúc (ADR)"). Xem skill `decision-recorder`.
52
+
53
+ ## Decisions (ADR) hiện có
54
+
55
+ Xem `docs/decisions/INDEX.md` — trí nhớ quyết định kiến trúc (chống flip-flop xuyên session). Đọc ADR liên quan TRƯỚC khi đổi kiến trúc một module.
49
56
 
50
57
  ## Plans hiện có
51
58
 
@@ -31,6 +31,7 @@
31
31
  - Status trong file: `Draft` (chờ user review) → `In Progress` (đã approve) → `Done`.
32
32
  - **Auto-archive**: ngay khi đổi Status → Done, chạy `git mv plans/<file>.md plans/archive/<file>.md` và update bảng Archived bên trên.
33
33
  - Plan template: 5 sections (Hiểu yêu cầu, Mục tiêu, Modules ảnh hưởng, Files ảnh hưởng, Tasks). Plan minimal chỉ dùng khi vẫn muốn audit cho task nhỏ.
34
+ - **Plan đổi/đảo kiến trúc** PHẢI có section "## Quyết định kiến trúc" (`Supersede ADR-NNNN` + lý do root cause cũ hết đúng) và sinh/cập nhật ADR trong `docs/decisions/` ở Bước 5. Xem skill `decision-recorder`.
34
35
  - Skill: `~/.cursor/skills/plan-writer/SKILL.md`.
35
36
 
36
37
  ## Ghi chú riêng của dự án