openmoneta-dev-kit 2.0.2 → 2.2.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
- 2.0.2
1
+ 2.2.0
@@ -59,7 +59,7 @@ SUMMARY='# OpenMoneta Dev Kit v1.7.0 — Quy trình 6 bước (Adaptive Planning
59
59
  - **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.
60
60
  - **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`.
61
61
  - **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.
62
- - **B5 Update doc + close** (hook `verify-completion` Check 6 enforce): sync module README (Public API + Dependencies). Plan → `Status: Done` + auto-archive `git mv plans/<f>.md plans/archive/`.
62
+ - **B5 Update doc + close** (hook `verify-completion` Check 6 enforce): chạm module nào → sync README module đó NGAY trong session (Trách nhiệm/Public API/Dependencies + ràng buộc; Check 6 nudge nếu README không đổi). Thay đổi chạm kiến trúc mà KHÔNG có plan → vẫn phải tạo ADR (`decision-recorder`); Check 12 nudge khi đổi đáng kể mà thiếu ADR. Plan → `Status: Done` + auto-archive `git mv plans/<f>.md plans/archive/`.
63
63
  - **B6 Pre-push Sync + Safe Push** (skill `safe-push`, CONDITIONAL): chỉ khi user yêu cầu push/commit+push. `git fetch` + rebase trước push, conflict code logic → hỏi user, verify lại, `git push` thường, retry ≤3, KHÔNG `--force` shared branch.
64
64
 
65
65
  ## Sub-agents (delegate khi cần)
@@ -113,12 +113,17 @@ if [[ -x "$LIST_MODULES_SCRIPT" || -f "$LIST_MODULES_SCRIPT" ]]; then
113
113
  AFFECTED=$(bash "$LIST_MODULES_SCRIPT" "$CHANGES_FILE" 2>/dev/null || true)
114
114
 
115
115
  if [[ -n "$AFFECTED" ]]; then
116
+ # Danh sách path đã đổi trong session (để check README freshness — Tuyến 1).
117
+ CHANGED_PATHS=$(jq -r '.changes[] | .path' "$CHANGES_FILE" 2>/dev/null || true)
116
118
  while IFS=$'\t' read -r slug docs_path source_path; do
117
119
  [[ -z "$slug" ]] && continue
118
120
  MODULE_README="$WORKSPACE/$docs_path/README.md"
119
121
 
120
122
  if [[ ! -f "$MODULE_README" ]]; then
121
123
  ISSUES+=("❌ Module '$slug' (source: $source_path) bị sửa nhưng chưa có '$docs_path/README.md'. Tạo README 3 sections (Trách nhiệm + Public API + Dependencies) theo skill module-architect Bước 2.2 + cập nhật bảng 'Modules hiện có' và 'Token Routing' trong docs/INDEX.md.")
124
+ elif [[ "$LOOP_COUNT" -eq 0 ]] && ! printf '%s\n' "$CHANGED_PATHS" | grep -qFx "$docs_path/README.md"; then
125
+ # README freshness (nudge mềm 1 lần): module source đổi nhưng README chưa sync trong session.
126
+ ISSUES+=("⚠️→ Module '$slug' bị sửa source nhưng '$docs_path/README.md' KHÔNG được cập nhật trong session. Nếu có đổi Public API / Trách nhiệm / Dependencies / ràng buộc → sync README NGAY (skill module-architect Bước 5). Nếu chỉ fix nội bộ không đổi hợp đồng → bỏ qua, kết thúc lại lần nữa (nhắc 1 lần).")
122
127
  fi
123
128
  done <<< "$AFFECTED"
124
129
 
@@ -159,16 +164,34 @@ if [[ -n "$CHANGED_DECISIONS" ]]; then
159
164
  fi
160
165
 
161
166
  # Check 11: plan trong session khai báo đổi/đảo kiến trúc nhưng KHÔNG sinh/sửa ADR
167
+ ARCH_DECLARED=0
162
168
  for plan in $ALL_RECENT_PLANS; do
163
169
  [[ -z "$plan" || ! -f "$plan" ]] && continue
164
170
  if grep -qE "^##[[:space:]]+Quyết định kiến trúc" "$plan" 2>/dev/null; then
165
171
  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.")
172
+ if [[ -n "$DECL" ]]; then
173
+ ARCH_DECLARED=1
174
+ if [[ -z "$CHANGED_DECISIONS" ]]; then
175
+ 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.")
176
+ fi
168
177
  fi
169
178
  fi
170
179
  done
171
180
 
181
+ # Check 12: thay đổi "đáng kể" KHÔNG qua plan kiến trúc + KHÔNG có ADR → nudge mềm 1 lần.
182
+ # Backstop cho ca sửa code chạm kiến trúc nhưng không tạo plan (Check 11 không bắt được).
183
+ # Ngưỡng CHẶT (≥4 module HOẶC ≥12 file code) để hiếm khi kêu, tránh false-positive.
184
+ if [[ "$LOOP_COUNT" -eq 0 && -f "$WORKSPACE/docs/decisions/INDEX.md" && -z "$CHANGED_DECISIONS" && "$ARCH_DECLARED" -eq 0 ]]; then
185
+ NUM_CODE_FILES=$(jq -r '.changes[] | .path' "$CHANGES_FILE" 2>/dev/null \
186
+ | grep -vE '^(docs/|plans/|README|\.gitignore|\.env\.example|AGENTS\.md|\.cursor/|CHANGELOG|VERSION$|package(-lock)?\.json|pnpm-lock|yarn\.lock)' \
187
+ | grep -cvE '^$' || true)
188
+ NUM_CODE_FILES=${NUM_CODE_FILES:-0}
189
+ NUM_MODULES=${NUM_MODULES:-0}
190
+ if [[ "$NUM_MODULES" -ge 4 || "$NUM_CODE_FILES" -ge 12 ]]; then
191
+ ISSUES+=("⚠️→ Session đụng $NUM_MODULES module / $NUM_CODE_FILES file code nhưng KHÔNG tạo/sửa ADR nào và KHÔNG có plan khai báo kiến trúc. NẾU đây là quyết định kiến trúc (đổi data flow, đổi cách module giao tiếp, thay/đảo 1 cơ chế, fix bug bằng cách đổi thiết kế) → tạo ADR (skill decision-recorder) hoặc nâng lên plan TRƯỚC khi đóng. Nếu chỉ là thay đổi thường (refactor nội bộ, đổi text, thêm field) → bỏ qua, kết thúc lại lần nữa (nhắc 1 lần).")
192
+ fi
193
+ fi
194
+
172
195
  # === Output ===
173
196
  WARN_STR=""
174
197
  if [[ ${#WARNINGS[@]} -gt 0 ]]; then
@@ -16,11 +16,11 @@
16
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
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
- - **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]`.
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]`. **Bug-fix hoá ra chạm kiến trúc** (đổi data flow / cách module giao tiếp / thay-đảo cơ chế) → nâng lên B3 hoặc tạo ADR, KHÔNG âm thầm sửa.
20
20
  - **B5 Update doc + close plan** — **mandatory closing checklist** (plugin guard `event:session.idle` sẽ re-prompt nếu thiếu):
21
- - [ ] Sync `docs/modules/<slug>/README.md` cho mọi module bị sửa: **Trách nhiệm + Public API + Dependencies**.
21
+ - [ ] Sync `docs/modules/<slug>/README.md` cho MỌI module bị sửa NGAY trong session: **Trách nhiệm + Public API + Dependencies** (+ **Lưu ý/Ràng buộc** nếu có invariant mới). Guard Check 6 nudge nếu module source đổi mà README không đổi.
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
+ - [ ] Nếu session đổi kiến trúc (kể cả KHÔNG có plan) → tạo/supersede ADR trong `docs/decisions/` + link vào README module + cập nhật `docs/decisions/INDEX.md` (skill `decision-recorder`). Guard Check 12 nudge khi đổi đáng kể (≥4 module / ≥12 file) mà thiếu ADR.
24
24
  - [ ] Plan → `Status: Done` + tick mọi checkbox.
25
25
  - [ ] **AUTO-ARCHIVE**: `git mv plans/<file>.md plans/archive/<file>.md` + update `plans/INDEX.md` (Active → Archived).
26
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.
@@ -32,7 +32,7 @@
32
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.
33
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`).
34
34
  - OpenMoneta subagents được cài vào `~/.config/opencode/agents/`.
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
+ - 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 tồn tại + freshness nudge (Check 6), close plan, supersede ADR (Check 10/11), ADR backstop khi đổi đáng kể không plan (Check 12), re-prompt nhắc hoàn tất với loop guard ≤4 lần).
36
36
 
37
37
  ## Khi Bắt Đầu Task
38
38
 
@@ -320,8 +320,10 @@ function planHasUnderstanding(planPath: string): boolean {
320
320
  return foundHeading && hasContent
321
321
  }
322
322
 
323
- // Build danh sách issue tương đương verify-completion Check 5/6/9.
324
- function buildVerifyIssues(root: string): string[] {
323
+ // Build danh sách issue tương đương verify-completion Check 5/6/9/10/11/12.
324
+ // firstNudge = true khi đây là lần re-prompt đầu (verify-loop count === 0) dùng cho
325
+ // các nudge mềm "1 lần" (README freshness + Check 12 ADR backstop), parity với LOOP_COUNT == 0.
326
+ function buildVerifyIssues(root: string, firstNudge = true): string[] {
325
327
  const paths = readSessionChanges(root)
326
328
  if (paths.length === 0) return []
327
329
  if (!paths.some((p) => !NON_CODE_CHANGE.test(p))) return []
@@ -366,11 +368,17 @@ function buildVerifyIssues(root: string): string[] {
366
368
  }
367
369
  }
368
370
 
369
- // Check 6: module bị sửa phải có docs/modules/<slug>/README.md
371
+ // Check 6: module bị sửa phải có docs/modules/<slug>/README.md (+ README freshness — Tuyến 1)
370
372
  for (const { slug, docsPath } of inferModules(paths)) {
371
- if (!fs.existsSync(path.join(root, docsPath, "README.md"))) {
373
+ const readmeRel = `${docsPath}/README.md`
374
+ if (!fs.existsSync(path.join(root, readmeRel))) {
372
375
  issues.push(
373
- `❌ Module '${slug}' bị sửa nhưng thiếu '${docsPath}/README.md'. Tạo README 3 sections (Trách nhiệm + Public API + Dependencies) + cập nhật docs/INDEX.md (Bước 2/5).`,
376
+ `❌ Module '${slug}' bị sửa nhưng thiếu '${readmeRel}'. Tạo README 3 sections (Trách nhiệm + Public API + Dependencies) + cập nhật docs/INDEX.md (Bước 2/5).`,
377
+ )
378
+ } else if (firstNudge && !paths.includes(readmeRel)) {
379
+ // README freshness (nudge mềm 1 lần): module source đổi nhưng README chưa sync.
380
+ issues.push(
381
+ `⚠️→ Module '${slug}' bị sửa source nhưng '${readmeRel}' KHÔNG được cập nhật trong session. Nếu có đổi Public API / Trách nhiệm / Dependencies / ràng buộc → sync README NGAY (Bước 5). Nếu chỉ fix nội bộ không đổi hợp đồng → bỏ qua, kết thúc lại lần nữa (nhắc 1 lần).`,
374
382
  )
375
383
  }
376
384
  }
@@ -403,6 +411,7 @@ function buildVerifyIssues(root: string): string[] {
403
411
  }
404
412
 
405
413
  // Check 11: plan khai báo "## Quyết định kiến trúc" (đổi/đảo) nhưng không sinh/sửa ADR
414
+ let archDeclared = false
406
415
  for (const plan of planSet) {
407
416
  if (!fs.existsSync(plan)) continue
408
417
  const lines = fs.readFileSync(plan, "utf8").split(/\r?\n/)
@@ -416,9 +425,32 @@ function buildVerifyIssues(root: string): string[] {
416
425
  if (inSection && /^##\s+/.test(line)) break
417
426
  if (inSection && /supersede|đổi kiến trúc|revert|đảo ngược/i.test(line)) declared = true
418
427
  }
419
- if (declared && changedDecisions.length === 0) {
428
+ if (declared) {
429
+ archDeclared = true
430
+ if (changedDecisions.length === 0) {
431
+ issues.push(
432
+ `❌ 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.`,
433
+ )
434
+ }
435
+ }
436
+ }
437
+
438
+ // Check 12: thay đổi "đáng kể" KHÔNG qua plan kiến trúc + KHÔNG có ADR → nudge mềm 1 lần.
439
+ // Backstop cho ca sửa code chạm kiến trúc nhưng không tạo plan. Ngưỡng CHẶT (≥4 module HOẶC
440
+ // ≥12 file code) để hiếm khi kêu, tránh false-positive.
441
+ if (
442
+ firstNudge &&
443
+ fs.existsSync(path.join(root, "docs", "decisions", "INDEX.md")) &&
444
+ changedDecisions.length === 0 &&
445
+ !archDeclared
446
+ ) {
447
+ const numModules = inferModules(paths).length
448
+ const numCodeFiles = paths.filter(
449
+ (p) => !/^(docs\/|plans\/|README|\.gitignore|\.env\.example|AGENTS\.md|\.cursor\/|CHANGELOG|VERSION$|package(-lock)?\.json|pnpm-lock|yarn\.lock)/.test(p),
450
+ ).length
451
+ if (numModules >= 4 || numCodeFiles >= 12) {
420
452
  issues.push(
421
- `❌ Plan '${path.basename(plan)}''## Quyết định kiến trúc' khai báo đổi/đảo kiến trúc nhưng KHÔNG docs/decisions/*.md được tạo/cập nhật. Tạo/supersede ADR (skill decision-recorder) trước khi kết thúc.`,
453
+ `⚠️→ Session đụng ${numModules} module / ${numCodeFiles} file code nhưng KHÔNG tạo/sửa ADR nào và KHÔNG plan khai báo kiến trúc. NẾU đây là quyết định kiến trúc (đổi data flow, đổi cách module giao tiếp, thay/đảo 1 chế, fix bug bằng cách đổi thiết kế) → tạo ADR (skill decision-recorder) hoặc nâng lên plan TRƯỚC khi đóng. Nếu chỉ là thay đổi thường → bỏ qua, kết thúc lại lần nữa (nhắc 1 lần).`,
422
454
  )
423
455
  }
424
456
  }
@@ -518,7 +550,7 @@ export const OpenMonetaGuard = async (ctx: GuardContext) => {
518
550
  {
519
551
  loaded_at: new Date().toISOString(),
520
552
  root,
521
- version: "2.0.2",
553
+ version: "2.2.0",
522
554
  load_count: globalState[globalKey],
523
555
  },
524
556
  null,
@@ -686,8 +718,12 @@ export const OpenMonetaGuard = async (ctx: GuardContext) => {
686
718
  }) => {
687
719
  if (event.type !== "session.idle") return
688
720
  try {
689
- const issues = buildVerifyIssues(root)
690
721
  const sessionID = asString(event.properties?.sessionID)
722
+ const loop = readVerifyLoop(root)
723
+ const count = loop.sessionID === sessionID ? loop.count : 0
724
+
725
+ // count === 0 ⇒ lần nhắc đầu ⇒ bật các nudge mềm "1 lần" (parity LOOP_COUNT == 0).
726
+ const issues = buildVerifyIssues(root, count === 0)
691
727
 
692
728
  if (issues.length === 0) {
693
729
  clearSessionChanges(root)
@@ -695,9 +731,6 @@ export const OpenMonetaGuard = async (ctx: GuardContext) => {
695
731
  return
696
732
  }
697
733
 
698
- const loop = readVerifyLoop(root)
699
- const count = loop.sessionID === sessionID ? loop.count : 0
700
-
701
734
  if (count >= VERIFY_LOOP_LIMIT) {
702
735
  console.warn(
703
736
  `[OpenMoneta Dev Kit] verify-completion: đã nhắc ${count} lần, graceful exit. ` +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmoneta-dev-kit",
3
- "version": "2.0.2",
3
+ "version": "2.2.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",
@@ -9,8 +9,11 @@
9
9
  # bash docs-audit.sh --report # như trên
10
10
  # bash docs-audit.sh --worklist # TSV: <slug>\t<docsPath>\t<sourcePath>\t<missing|stale|ok>
11
11
  # bash docs-audit.sh --validate # liệt kê GHOST (doc trỏ source/module không tồn tại)
12
+ # bash docs-audit.sh --decisions # audit ADR (docs/decisions/): legacy / index-sync / supersede / module-link
13
+ # bash docs-audit.sh --decisions-worklist # TSV: <issue>\t<path> cho skill xử lý
12
14
  #
13
15
  # Stale detection: so commit mới nhất của source vs README (git); fallback mtime filesystem.
16
+ # ADR audit chỉ MECHANICAL (không bịa nội dung): phát hiện nợ ADR, không tự sửa.
14
17
 
15
18
  set -uo pipefail
16
19
 
@@ -18,8 +21,10 @@ MODE="report"
18
21
  case "${1:-}" in
19
22
  --worklist) MODE="worklist" ;;
20
23
  --validate) MODE="validate" ;;
24
+ --decisions) MODE="decisions" ;;
25
+ --decisions-worklist) MODE="decisions-worklist" ;;
21
26
  --report|"") MODE="report" ;;
22
- *) echo "[!] Mode không hợp lệ: $1 (dùng --report|--worklist|--validate)" >&2; exit 2 ;;
27
+ *) echo "[!] Mode không hợp lệ: $1 (dùng --report|--worklist|--validate|--decisions|--decisions-worklist)" >&2; exit 2 ;;
23
28
  esac
24
29
 
25
30
  GIT_OK=0
@@ -100,6 +105,125 @@ slug_to_source() {
100
105
  fi
101
106
  }
102
107
 
108
+ # ---------- MODE: decisions / decisions-worklist (ADR audit) ----------
109
+ if [[ "$MODE" == "decisions" || "$MODE" == "decisions-worklist" ]]; then
110
+ WL=0; [[ "$MODE" == "decisions-worklist" ]] && WL=1
111
+ DDIR="docs/decisions"
112
+
113
+ emit() { # <issue> <path-or-msg>
114
+ if [[ "$WL" -eq 1 ]]; then printf '%s\t%s\n' "$1" "$2"; else printf ' %-16s %s\n' "$1" "$2"; fi
115
+ }
116
+
117
+ # ADR đúng format = NNNN-slug.md (NNNN sequential). Loại trừ file date YYYY-MM-DD-slug.md (legacy).
118
+ is_adr_file() {
119
+ local b="$1"
120
+ [[ "$b" =~ ^[0-9][0-9][0-9][0-9]-.*\.md$ ]] || return 1
121
+ [[ "$b" =~ ^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]- ]] && return 1
122
+ return 0
123
+ }
124
+ adr_files() { # in ra path các ADR đúng format
125
+ local f b
126
+ for f in "$DDIR"/[0-9][0-9][0-9][0-9]-*.md; do
127
+ [[ -f "$f" ]] || continue
128
+ b=$(basename "$f")
129
+ is_adr_file "$b" && echo "$f"
130
+ done
131
+ }
132
+
133
+ if [[ ! -d "$DDIR" ]]; then
134
+ [[ "$WL" -eq 0 ]] && echo " Project chưa có layer Decision Memory (docs/decisions/). Bỏ qua ADR audit."
135
+ exit 0
136
+ fi
137
+
138
+ n_legacy=0; n_orphan=0; n_ghost=0; n_supersede=0; n_modlink=0
139
+
140
+ # (1) Legacy: file trong docs/decisions/ không theo NNNN-slug.md (ngoài INDEX.md)
141
+ for f in "$DDIR"/*; do
142
+ [[ -f "$f" ]] || continue
143
+ b=$(basename "$f")
144
+ [[ "$b" == "INDEX.md" ]] && continue
145
+ if ! is_adr_file "$b"; then
146
+ emit "LEGACY" "$f"
147
+ n_legacy=$((n_legacy+1))
148
+ fi
149
+ done
150
+
151
+ # (2a) ADR file đúng format nhưng KHÔNG có trong bảng INDEX
152
+ while IFS= read -r f; do
153
+ [[ -z "$f" ]] && continue
154
+ b=$(basename "$f")
155
+ if [[ -f "$DDIR/INDEX.md" ]] && grep -qF "$b" "$DDIR/INDEX.md" 2>/dev/null; then :; else
156
+ emit "NOT-IN-INDEX" "$f"
157
+ n_orphan=$((n_orphan+1))
158
+ fi
159
+ done < <(adr_files)
160
+
161
+ # (2b) INDEX trỏ ADR file đã mất
162
+ if [[ -f "$DDIR/INDEX.md" ]]; then
163
+ while IFS= read -r ref; do
164
+ [[ -z "$ref" ]] && continue
165
+ [[ -f "$DDIR/$ref" ]] || { emit "INDEX-GHOST" "$DDIR/INDEX.md → $ref"; n_ghost=$((n_ghost+1)); }
166
+ done < <(grep -oE '[0-9]{4}-[A-Za-z0-9._-]+\.md' "$DDIR/INDEX.md" 2>/dev/null | sort -u)
167
+ fi
168
+
169
+ # (3) Supersede integrity
170
+ while IFS= read -r f; do
171
+ [[ -z "$f" ]] && continue
172
+ sup=$(grep -oE '^\*\*Supersedes\*\*:[[:space:]]*ADR-[0-9]{4}' "$f" 2>/dev/null | grep -oE '[0-9]{4}' | head -1)
173
+ [[ -z "$sup" ]] && continue
174
+ target=$(ls "$DDIR/${sup}-"*.md 2>/dev/null | head -1)
175
+ if [[ -z "$target" ]]; then
176
+ emit "SUPERSEDE-MISS" "$(basename "$f") → ADR-$sup không tồn tại"
177
+ n_supersede=$((n_supersede+1))
178
+ elif ! grep -qE '^\*\*Status\*\*:.*Superseded by' "$target" 2>/dev/null; then
179
+ emit "SUPERSEDE-OPEN" "$(basename "$target") chưa đổi Status sang 'Superseded by' (bị $(basename "$f") thay)"
180
+ n_supersede=$((n_supersede+1))
181
+ fi
182
+ done < <(adr_files)
183
+
184
+ # (4) Module link (advisory): ADR khai Module(s) có docs/modules/<slug>/README.md nhưng chưa link ADR
185
+ while IFS= read -r f; do
186
+ [[ -z "$f" ]] && continue
187
+ b=$(basename "$f")
188
+ mods=$(grep -oE '^\*\*Module\(s\)\*\*:.*' "$f" 2>/dev/null | sed -E 's/^\*\*Module\(s\)\*\*:[[:space:]]*//')
189
+ [[ -z "$mods" ]] && continue
190
+ IFS=',' read -ra arr <<< "$mods"
191
+ for m in "${arr[@]}"; do
192
+ slug=$(echo "$m" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')
193
+ [[ -z "$slug" || "$slug" == "—" ]] && continue
194
+ readme="docs/modules/$slug/README.md"
195
+ [[ -f "$readme" ]] || continue
196
+ if ! grep -qF "$b" "$readme" 2>/dev/null; then
197
+ emit "MODLINK" "$readme chưa link $b (Module(s) của ADR)"
198
+ n_modlink=$((n_modlink+1))
199
+ fi
200
+ done
201
+ done < <(adr_files)
202
+
203
+ if [[ "$WL" -eq 1 ]]; then exit 0; fi
204
+
205
+ total=$((n_legacy+n_orphan+n_ghost+n_supersede))
206
+ echo ""
207
+ echo " ADR audit — $(pwd)/$DDIR"
208
+ echo " ──────────────────────────────────────────────────────────────"
209
+ if [[ "$total" -eq 0 && "$n_modlink" -eq 0 ]]; then
210
+ echo " ✓ Sạch: ADR đăng ký đầy đủ trong INDEX, không legacy/ghost, supersede toàn vẹn."
211
+ else
212
+ echo " Tổng: $total vấn đề cần xử lý"
213
+ echo " · $n_legacy legacy (file không theo NNNN-slug.md, chưa đăng ký)"
214
+ echo " · $n_orphan ADR chưa có trong INDEX"
215
+ echo " · $n_ghost INDEX trỏ ADR đã mất"
216
+ echo " · $n_supersede supersede không toàn vẹn"
217
+ [[ "$n_modlink" -gt 0 ]] && echo " · $n_modlink module README chưa link ADR (advisory)"
218
+ echo ""
219
+ echo " → Để MIGRATE legacy (reformat file có sẵn nội dung) + đăng ký INDEX:"
220
+ echo " mở Cursor/OpenCode và yêu cầu chạy skill 'docs-maintenance' (phạm vi ADR)."
221
+ echo " → ADR còn THIẾU cho quyết định CHƯA ghi: dùng skill 'decision-recorder' (cần ngữ cảnh thật, KHÔNG bịa từ code)."
222
+ fi
223
+ echo ""
224
+ exit 0
225
+ fi
226
+
103
227
  MODULES=$(collect_modules | sort -u)
104
228
 
105
229
  # ---------- MODE: validate (ghost) ----------
@@ -19,8 +19,12 @@ KHÔNG ĐẢO NGƯỢC một kiến trúc đã Accepted (LOCKED) khi CHƯA:
19
19
 
20
20
  ## Khi nào TẠO ADR
21
21
 
22
+ > **Trigger này áp dụng kể cả KHÔNG có plan.** Nếu đang fix bug / sửa code mà chạm 1 trong các dấu hiệu dưới → DỪNG, tạo ADR NGAY (hoặc nâng lên plan), KHÔNG âm thầm sửa rồi đóng session. Hook Check 12 sẽ nudge nếu thay đổi đáng kể mà thiếu ADR.
23
+
22
24
  - 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).
25
+ - Đổi cách triển khai một tính năng đang chạy (vd: polling → websocket; DB heartbeat → in-memory).
26
+ - **Đổi data flow / đổi cách các module giao tiếp với nhau** (đổi contract giữa module, đổi nguồn sự thật).
27
+ - **Thay/đảo một cơ chế cốt lõi** dù khởi đầu chỉ là "fix bug" — fix bug bằng cách ĐỔI THIẾT KẾ = quyết định kiến trúc.
24
28
  - `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
29
  - Đảo ngược / thay thế một quyết định cũ.
26
30
 
@@ -1,22 +1,33 @@
1
1
  ---
2
2
  name: docs-maintenance
3
- description: "ON-DEMAND ONLY: dùng khi user yêu cầu tạo docs còn thiếu / backfill module README / refresh docs lỗi thời / audit docs / bảo trì docs dự án cũ. KHÔNG tự trigger trong quy trình bình thường."
3
+ description: "ON-DEMAND ONLY: dùng khi user yêu cầu tạo docs còn thiếu / backfill module README / refresh docs lỗi thời / audit docs / bảo trì docs dự án / audit & migrate ADR legacy. KHÔNG tự trigger trong quy trình bình thường."
4
4
  ---
5
5
 
6
6
  # Docs Maintenance
7
7
 
8
- Quét cả repo một lượt để **backfill module README còn thiếu** và **refresh README đã lỗi thời** so với code, rồi **đồng bộ `docs/INDEX.md`** (Modules hiện có + Token Routing).
8
+ Quét cả repo một lượt để **backfill module README còn thiếu** và **refresh README đã lỗi thời** so với code, rồi **đồng bộ `docs/INDEX.md`** (Modules hiện có + Token Routing). Ngoài ra **audit + migrate ADR legacy** trong `docs/decisions/` (phần mechanical, KHÔNG bịa rationale).
9
9
 
10
10
  Dùng khi onboard/bảo trì **dự án cũ** đã tích lũy nợ doc (code đụng nhiều nhưng README không được cập nhật), hoặc khi user yêu cầu rõ. Đây là phiên bản "chạy hàng loạt cho cả repo" của skill `module-architect` (vốn chạy từng module/session).
11
11
 
12
+ ## Hai phạm vi tách bạch
13
+
14
+ | Phạm vi | Nguồn chân lý | Skill này làm | KHÔNG làm |
15
+ |---|---|---|---|
16
+ | **Module README** (WHAT) | Source code (verify bằng grep) | Backfill MISSING + refresh STALE + sync INDEX | — |
17
+ | **ADR** (WHY/WHY-NOT) | Phán đoán kiến trúc của con người | Audit mechanical (legacy/index-sync/supersede/module-link) + **migrate file legacy ĐÃ CÓ nội dung** | **KHÔNG bịa rationale ADR từ code**; tạo ADR cho quyết định CHƯA ghi → `decision-recorder` |
18
+
12
19
  ## Iron Law
13
20
 
14
21
  ```
15
- CHỈ VIẾT NHỮNG GÌ VERIFY ĐƯỢC TRONG CODE. KHÔNG BỊA API / PATH / DEPENDENCY.
22
+ CHỈ VIẾT NHỮNG GÌ VERIFY ĐƯỢC TRONG NGUỒN. KHÔNG BỊA.
23
+ - Module README: mỗi dòng truy về symbol/import có thật trong source.
24
+ - ADR: mỗi rationale truy về nội dung file legacy ĐÃ CÓ. KHÔNG suy diễn WHY/root-cause từ code hiện tại.
16
25
  ```
17
26
 
18
27
  Stale docs còn tệ hơn không có docs — session sau AI đọc sai, làm sai. Mỗi dòng trong README phải truy về một symbol/import có thật trong source. Không chắc → mô tả mức ý định cao, KHÔNG bịa signature.
19
28
 
29
+ **ADR bịa còn nguy hiểm hơn**: ADR giả tạo ra "trí nhớ quyết định" sai → session sau tin theo và đảo kiến trúc sai hướng — phá đúng cái Decision Memory muốn chống. KHÔNG bao giờ chế WHY/rejected-alternatives/root-cause từ code. File legacy không đủ rationale → BÁO user, KHÔNG tự điền.
30
+
20
31
  ## Ranh giới (Lean — KHÔNG làm gì)
21
32
 
22
33
  - **KHÔNG** sinh bộ doc tổng quan kiểu khác (`system-architecture.md`, `project-roadmap.md`, `codebase-summary.md`...). Mô hình OM là **per-module README 3 sections + `docs/INDEX.md`**, đủ để navigate token-aware.
@@ -81,6 +92,48 @@ Cho mỗi module status `stale`:
81
92
 
82
93
  In tóm tắt: `created / updated / skipped` + cảnh báo (module không verify được, ghost). Gợi ý chạy lại `openmoneta docs --validate` để xác nhận sạch.
83
94
 
95
+ ## ADR audit + migrate legacy (docs/decisions/)
96
+
97
+ Chạy khi user yêu cầu "audit ADR", "bảo trì decisions", "migrate quyết định cũ", hoặc khi onboard dự án có `docs/decisions/` lộn xộn. Bỏ qua nếu project chưa có `docs/decisions/`.
98
+
99
+ ### 1. Audit (deterministic)
100
+
101
+ ```bash
102
+ bash ~/.cursor/scripts/docs-audit.sh --decisions # report 4 loại vấn đề
103
+ bash ~/.cursor/scripts/docs-audit.sh --decisions-worklist # TSV: <issue>\t<path>
104
+ ```
105
+
106
+ (OpenCode: `~/.config/opencode/scripts/docs-audit.sh`.) 4 loại issue:
107
+
108
+ - **LEGACY**: file trong `docs/decisions/` không theo `NNNN-slug.md` (vd `YYYY-MM-DD-*.md`) → ứng viên migrate.
109
+ - **NOT-IN-INDEX**: ADR đúng format nhưng thiếu dòng trong `INDEX.md`.
110
+ - **INDEX-GHOST**: `INDEX.md` trỏ ADR file đã mất.
111
+ - **SUPERSEDE-MISS / SUPERSEDE-OPEN**: `Supersedes: ADR-XXXX` trỏ ADR không tồn tại, hoặc ADR cũ chưa đổi Status sang `Superseded by`.
112
+ - **MODLINK** (advisory): module README chưa link ADR khai trong `Module(s)`.
113
+
114
+ ### 2. Migrate LEGACY → ADR chuẩn (CHỈ reformat, KHÔNG bịa)
115
+
116
+ Cho mỗi file LEGACY:
117
+
118
+ 1. **Đọc file legacy** — nó ĐÃ CÓ rationale thật. KHÔNG suy diễn từ code.
119
+ 2. Xác định `NNNN` mới = max ADR hiện có trong INDEX + 1 (4 chữ số).
120
+ 3. Tạo `docs/decisions/NNNN-<slug>.md` theo `~/.cursor/templates/adr.md.tpl`, **map nội dung có sẵn** vào 5 section (Context/Decision/Rejected alternatives/Consequences/Failed approaches log). Phần nào file cũ KHÔNG có → để trống + ghi `(legacy: không ghi rõ)`, KHÔNG chế ra.
121
+ 4. Field: `Status` (giữ theo file cũ nếu có, mặc định `Accepted (LOCKED)`), `Date` (lấy từ tên file/nội dung cũ), `Module(s)` (nếu suy được từ nội dung), `Plan nguồn: —`.
122
+ 5. Xóa file legacy cũ (đã chuyển nội dung) — hoặc nếu format cũ là chuẩn nội bộ của dự án, hỏi user trước khi xóa.
123
+ 6. Thêm dòng vào bảng `INDEX.md`.
124
+
125
+ ### 3. Sửa NOT-IN-INDEX / INDEX-GHOST / SUPERSEDE / MODLINK
126
+
127
+ - **NOT-IN-INDEX**: đọc ADR → thêm dòng vào bảng INDEX (id, tiêu đề, status, module, date).
128
+ - **INDEX-GHOST**: dòng INDEX trỏ file mất → hỏi user (xóa dòng hay khôi phục file). KHÔNG tự xóa.
129
+ - **SUPERSEDE-OPEN**: đổi ADR cũ sang `Status: Superseded by ADR-NNNN` (mechanical, an toàn).
130
+ - **SUPERSEDE-MISS**: target không tồn tại → BÁO user (có thể typo hoặc file mất), KHÔNG đoán.
131
+ - **MODLINK**: thêm link ADR vào section "## Quyết định kiến trúc (ADR)" của module README.
132
+
133
+ ### 4. Báo nếu thiếu rationale
134
+
135
+ File legacy chỉ có tiêu đề/1 dòng, không đủ WHY → migrate phần có được, **liệt kê rõ cho user** ADR nào cần bổ sung rationale qua `decision-recorder`. KHÔNG tự bịa cho đủ section.
136
+
84
137
  ## Bảng Rationalization (cái cớ → sự thật)
85
138
 
86
139
  | Cái cớ | Sự thật |
@@ -90,6 +143,8 @@ In tóm tắt: `created / updated / skipped` + cảnh báo (module không verify
90
143
  | "Viết lại cả README cho gọn" | Phá nội dung đang đúng + tốn token. Chỉ sửa phần lệch. |
91
144
  | "Thêm doc tổng quan cho đầy đủ" | Trái Lean. OM chỉ module README + INDEX. |
92
145
  | "Module mới khỏi thêm Token Routing" | Không có keyword → AI không tìm thấy module → đọc loạn. Bắt buộc thêm. |
146
+ | "File legacy thiếu WHY, mình suy từ code cho đủ ADR" | Bịa rationale = trí nhớ quyết định giả = tệ hơn không có. Để trống + báo user. |
147
+ | "Tạo luôn ADR cho quyết định chưa ai ghi" | Sai skill. Đó là việc của `decision-recorder` (cần ngữ cảnh thật). |
93
148
 
94
149
  ## Red Flags — DỪNG
95
150
 
@@ -98,9 +153,14 @@ In tóm tắt: `created / updated / skipped` + cảnh báo (module không verify
98
153
  - Đang định sinh `system-architecture.md` / `codebase-summary.md`.
99
154
  - Đang định xóa GHOST mà chưa hỏi user.
100
155
  - Tạo module mới trong INDEX nhưng quên Token Routing keyword.
156
+ - Đang điền section Rejected-alternatives/root-cause của ADR mà chưa thấy nó trong file legacy.
157
+ - Đang tạo ADR mới cho quyết định chưa từng được ghi (→ việc của `decision-recorder`).
158
+ - Đang xóa file legacy/INDEX-GHOST mà chưa hỏi user.
101
159
 
102
160
  ## Examples
103
161
 
104
162
  - User: "Dự án này nhiều module thiếu docs, tạo giúp" → `init`: chạy worklist → tạo mọi MISSING + refresh STALE → sync INDEX → report.
105
163
  - User: "Doc còn khớp code không?" → `audit`: chỉ report.
106
164
  - User: "Cập nhật doc 3 module vừa refactor" → `sync` (giới hạn các slug đó).
165
+ - User: "Audit ADR / decisions lộn xộn" → `docs-audit.sh --decisions` → report 4 loại issue.
166
+ - User: "Migrate mấy file quyết định cũ vào ADR chuẩn" → đọc từng file legacy → reformat sang `NNNN-slug.md` + đăng ký INDEX (không bịa phần thiếu, báo user).
@@ -70,7 +70,7 @@ Khi nào IGNORE rule (không tách dù dài):
70
70
  - Config files (webpack, playwright, biome, ...).
71
71
  - File có cohesion cực cao (vd 1 state machine 500 dòng cho 1 workflow rõ ràng).
72
72
 
73
- #### 3. Mỗi module có `README.md` riêng (3 sections, lean)
73
+ #### 3. Mỗi module có `README.md` riêng (3 section bắt buộc + 2 tùy, lean)
74
74
 
75
75
  ```markdown
76
76
  # <Tên module>
@@ -82,13 +82,27 @@ Khi nào IGNORE rule (không tách dù dài):
82
82
  - `funcA(arg1: X, arg2: Y): Z` — mô tả 1 dòng
83
83
  - `class Foo` — mô tả 1 dòng
84
84
 
85
+ ## Lưu ý / Ràng buộc (invariants) <!-- TÙY: chỉ khi có ràng buộc không hiển nhiên -->
86
+
87
+ - <quy tắc phải giữ / cạm bẫy, vd: "phải init DB trước khi gọi", "chạy cả Cursor + OpenCode nên giữ path trừu tượng">
88
+
89
+ ## Quyết định kiến trúc (ADR) <!-- TÙY: chỉ khi module có ADR -->
90
+
91
+ - [ADR-0003](../../decisions/0003-...md) — <1 dòng> (Accepted)
92
+
85
93
  ## Dependencies
86
94
 
87
95
  - Internal: `modules/auth`, `modules/db`
88
96
  - External: `axios`, `zod`
89
97
  ```
90
98
 
91
- > Chỉ 3 sections. KHÔNG thêm Owner/Stability/Cách dùng/Anti-patterns vào README mặc định — chúng dễ stale và AI hiếm khi dùng. Nếu module có quirk đặc biệt (vd phải init theo thứ tự), thêm section `## Lưu ý` ngắn ≤5 bullet.
99
+ **3 section BẮT BUỘC**: Trách nhiệm + Public API + Dependencies.
100
+
101
+ **2 section TÙY** (thêm khi có, vì là trí nhớ chống regression — đừng bỏ nếu thật sự có):
102
+ - **`## Lưu ý / Ràng buộc (invariants)`** (≤5 bullet): ràng buộc/cạm bẫy ngầm mà session sau dễ phá nếu không biết (thứ tự init, side-effect, môi trường chạy, giả định dữ liệu). Đây là tri thức ngầm hay mất giữa các session → gây regression; ghi ngắn gọn ngay khi phát hiện.
103
+ - **`## Quyết định kiến trúc (ADR)`**: list ADR-NNNN chi phối module (link). Tạo qua skill `decision-recorder`.
104
+
105
+ > KHÔNG thêm Owner/Stability/Cách dùng/tutorial — dễ stale, AI hiếm dùng. Giữ README lean: chỉ ghi cái session sau THỰC SỰ cần để không hiểu lệch / không phá đồ.
92
106
 
93
107
  ### Cấu trúc folder mẫu
94
108
 
@@ -204,7 +218,7 @@ Quy tắc maintenance:
204
218
 
205
219
  ### Bước 5 workflow — Sync module README (BẮT BUỘC)
206
220
 
207
- > Hook `verify-completion` Check 6 sẽ **CHẶN** session kết thúc nếu module bị sửa nhưng không `docs/modules/<slug>/README.md`.
221
+ > Hook `verify-completion` Check 6: **CHẶN** nếu module bị sửa nhưng thiếu `docs/modules/<slug>/README.md`; và **nudge mềm 1 lần** (README freshness) nếu module source đổi nhưng README KHÔNG được cập nhật trong session. Chạm module nào → sync README module đó NGAY trong session, KHÔNG để "sửa sau".
208
222
 
209
223
  **Cách xác định module từ file path** (cho file đã sửa trong session):
210
224
 
@@ -227,6 +241,7 @@ Quy tắc maintenance:
227
241
  4. Check section **Dependencies**:
228
242
  - Có `import` mới từ module khác? → thêm.
229
243
  - Có `import` bị xóa? → xóa.
244
+ 4b. Check section **Lưu ý / Ràng buộc**: thay đổi có tạo ra ràng buộc/cạm bẫy ngầm mới (thứ tự init, side-effect, giả định dữ liệu)? → thêm bullet ngắn. Đây là thứ chống regression cho session sau.
230
245
  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
246
  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.
232
247
 
@@ -47,6 +47,7 @@ description: "Dùng khi bắt đầu phân tích một yêu cầu trước khi t
47
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
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
49
  - Muốn đổi kiến trúc đã khóa → áp dụng skill `decision-recorder` (quy trình supersede tường minh).
50
+ - **Bug-fix hoá ra chạm kiến trúc**: nếu trong lúc làm 1 task tưởng nhỏ (fix bug) mà phát hiện phải đổi data flow / đổi cách module giao tiếp / thay-đảo cơ chế cốt lõi → KHÔNG âm thầm sửa rồi đóng. Nâng lên B3 (tạo plan) HOẶC tạo ADR ngay (skill `decision-recorder`), kể cả khi task khởi đầu không có plan.
50
51
 
51
52
  #### Anti-pattern token waste
52
53
 
package/src/cli.js CHANGED
@@ -56,6 +56,7 @@ Biến Cursor IDE / OpenCode thành team developer hoàn chỉnh.
56
56
 
57
57
  openmoneta docs Audit module README (thiếu/lỗi thời) + sync INDEX
58
58
  openmoneta docs --validate Phát hiện doc trỏ module/source không tồn tại (ghost)
59
+ openmoneta docs --decisions Audit ADR (docs/decisions/): legacy/index-sync/supersede/module-link
59
60
 
60
61
  openmoneta uninstall Gỡ cài đặt
61
62
 
@@ -6,6 +6,8 @@ const { getPkgRoot, isWindows } = require("../lib/paths")
6
6
  async function run(args) {
7
7
  const validate = args.includes("--validate")
8
8
  const worklist = args.includes("--worklist")
9
+ const decisions = args.includes("--decisions")
10
+ const decisionsWorklist = args.includes("--decisions-worklist")
9
11
 
10
12
  const pkgRoot = getPkgRoot()
11
13
  const script = path.join(pkgRoot, "scripts", "docs-audit.sh")
@@ -17,7 +19,9 @@ async function run(args) {
17
19
  }
18
20
 
19
21
  let mode = "--report"
20
- if (validate) mode = "--validate"
22
+ if (decisionsWorklist) mode = "--decisions-worklist"
23
+ else if (decisions) mode = "--decisions"
24
+ else if (validate) mode = "--validate"
21
25
  else if (worklist) mode = "--worklist"
22
26
 
23
27
  try {
@@ -41,10 +41,12 @@
41
41
 
42
42
  ## Cách thêm module mới
43
43
 
44
- 1. Tạo `docs/modules/<module-name>/README.md` với 3 sections:
44
+ 1. Tạo `docs/modules/<module-name>/README.md` với 3 section BẮT BUỘC + 2 section TÙY:
45
45
  - **Trách nhiệm**: 1 câu không có "và".
46
46
  - **Public API**: function/class export.
47
47
  - **Dependencies**: internal + external.
48
+ - *(tùy)* **Lưu ý / Ràng buộc (invariants)**: ≤5 bullet ràng buộc/cạm bẫy ngầm (thứ tự init, side-effect, giả định dữ liệu) — thêm khi có, để session sau không phá đồ.
49
+ - *(tùy)* **Quyết định kiến trúc (ADR)**: link ADR-NNNN chi phối module (xem `docs/decisions/` + skill `decision-recorder`).
48
50
  2. Update bảng "Modules hiện có" ở trên.
49
51
  3. **BẮT BUỘC**: thêm 3-5 keyword vào bảng "Token Routing" (cho cả VN + EN).
50
52
  4. Tạo source folder tương ứng (`src/modules/<name>/` hoặc `apps/<name>/` hoặc `packages/<name>/`).