openmoneta-dev-kit 1.12.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.12.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
@@ -10,12 +10,31 @@ set -euo pipefail
10
10
 
11
11
  INPUT_JSON=$(cat 2>/dev/null || echo '{}')
12
12
 
13
- # Reset docs-index marker mỗi session (force re-read INDEX.md)
13
+ # Auto-register project vào Cursor registry (seed cho `update --all-projects`).
14
+ # Im lặng. Chỉ append khi: workspace có docs/INDEX.md, Cursor install home đã cài,
15
+ # và path chưa có trong registry (dedup, chuẩn hoá pwd -P).
16
+ register_project_cursor() {
17
+ local ws="$1"
18
+ [[ -n "$ws" && -f "$ws/docs/INDEX.md" ]] || return 0
19
+ [[ -f "$HOME/.cursor/.openmoneta-version" ]] || return 0
20
+ local reg="$HOME/.cursor/.openmoneta-projects"
21
+ local abs
22
+ abs="$(cd "$ws" 2>/dev/null && pwd -P)" || return 0
23
+ [[ -n "$abs" ]] || return 0
24
+ if [[ -f "$reg" ]] && grep -qFx "$abs" "$reg" 2>/dev/null; then
25
+ return 0
26
+ fi
27
+ echo "$abs" >> "$reg" 2>/dev/null || true
28
+ }
29
+
30
+ # Reset docs-index + decisions marker mỗi session (force re-read INDEX.md + decisions/)
14
31
  if command -v jq >/dev/null 2>&1; then
15
32
  WORKSPACE_FROM_INPUT=$(echo "$INPUT_JSON" | jq -r '.workspace_roots[0] // ""' 2>/dev/null || echo "")
16
33
  if [[ -n "$WORKSPACE_FROM_INPUT" && -d "$WORKSPACE_FROM_INPUT/.cursor" ]]; then
17
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
18
36
  fi
37
+ register_project_cursor "$WORKSPACE_FROM_INPUT" 2>/dev/null || true
19
38
  fi
20
39
 
21
40
  # === Summary 6 bước (~45 dòng, ~650 tokens) ===
@@ -32,7 +51,7 @@ SUMMARY='# OpenMoneta Dev Kit v1.7.0 — Quy trình 6 bước (Adaptive Planning
32
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.
33
52
 
34
53
  ## 6 bước
35
- - **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`).
36
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.
37
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`.
38
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
 
@@ -400,6 +448,33 @@ function clearVerifyLoop(root: string) {
400
448
  fs.rmSync(path.join(root, ".cursor", ".openmoneta-verify-loop.json"), { force: true })
401
449
  }
402
450
 
451
+ // Auto-register project vào OpenCode registry (seed cho `update --all-projects`).
452
+ // Im lặng + try/catch — seed không được phép làm fail guard. Chỉ append khi:
453
+ // project có docs/INDEX.md, OpenCode install home đã cài, path chưa có trong registry.
454
+ function registerProjectOpenCode(root: string) {
455
+ try {
456
+ if (!fs.existsSync(path.join(root, "docs", "INDEX.md"))) return
457
+ const home = process.env.HOME || process.env.USERPROFILE || ""
458
+ const xdg = process.env.XDG_CONFIG_HOME || path.join(home, ".config")
459
+ const ocHome = path.join(xdg, "opencode")
460
+ if (!fs.existsSync(path.join(ocHome, ".openmoneta-version"))) return
461
+ const reg = path.join(ocHome, ".openmoneta-projects")
462
+ const abs = fs.realpathSync(root)
463
+ let lines: string[] = []
464
+ if (fs.existsSync(reg)) {
465
+ lines = fs
466
+ .readFileSync(reg, "utf8")
467
+ .split("\n")
468
+ .map((l) => l.trim())
469
+ .filter(Boolean)
470
+ }
471
+ if (lines.includes(abs)) return
472
+ fs.appendFileSync(reg, `${abs}\n`)
473
+ } catch {
474
+ // im lặng
475
+ }
476
+ }
477
+
403
478
  type SessionPromptClient = {
404
479
  session?: {
405
480
  prompt?: (input: {
@@ -423,8 +498,10 @@ export const OpenMonetaGuard = async (ctx: GuardContext) => {
423
498
  const client = ctx.client
424
499
  const root = projectRoot(ctx)
425
500
  const marker = path.join(root, ".cursor", ".docs-index-read")
501
+ const decisionsMarker = path.join(root, ".cursor", ".decisions-read")
426
502
  const guardLoadedMarker = path.join(root, ".cursor", ".openmoneta-guard-loaded.json")
427
503
  const docsIndex = path.join(root, "docs", "INDEX.md")
504
+ const decisionsIndex = path.join(root, "docs", "decisions", "INDEX.md")
428
505
  const pendingEdits = new Map<string, string[]>()
429
506
 
430
507
  fs.mkdirSync(path.dirname(marker), { recursive: true })
@@ -434,7 +511,7 @@ export const OpenMonetaGuard = async (ctx: GuardContext) => {
434
511
  {
435
512
  loaded_at: new Date().toISOString(),
436
513
  root,
437
- version: "1.11.0",
514
+ version: "2.0.0",
438
515
  load_count: globalState[globalKey],
439
516
  },
440
517
  null,
@@ -442,11 +519,17 @@ export const OpenMonetaGuard = async (ctx: GuardContext) => {
442
519
  )}\n`,
443
520
  )
444
521
 
522
+ // Seed project vào OpenCode registry để `update --all-projects` thấy project này.
523
+ registerProjectOpenCode(root)
524
+
445
525
  // OpenCode does not have Cursor's sessionStart hook, so reset the docs-first
446
- // 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.
447
527
  if (fs.existsSync(marker)) {
448
528
  fs.rmSync(marker, { force: true })
449
529
  }
530
+ if (fs.existsSync(decisionsMarker)) {
531
+ fs.rmSync(decisionsMarker, { force: true })
532
+ }
450
533
 
451
534
  return {
452
535
  "tool.execute.before": async (
@@ -463,6 +546,11 @@ export const OpenMonetaGuard = async (ctx: GuardContext) => {
463
546
  fs.writeFileSync(marker, new Date().toISOString())
464
547
  return
465
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
+ }
466
554
  if (shouldCheckRead(tool, target) && !fs.existsSync(marker)) {
467
555
  fail(
468
556
  [
@@ -475,6 +563,20 @@ export const OpenMonetaGuard = async (ctx: GuardContext) => {
475
563
  ].join("\n"),
476
564
  )
477
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
+ }
478
580
  }
479
581
 
480
582
  if (isBashTool(tool) && fs.existsSync(docsIndex)) {
@@ -484,6 +586,11 @@ export const OpenMonetaGuard = async (ctx: GuardContext) => {
484
586
  fs.writeFileSync(marker, new Date().toISOString())
485
587
  return
486
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
+ }
487
594
  if (isSourceSearchCommand(command) && !fs.existsSync(marker)) {
488
595
  fail(
489
596
  [
@@ -498,6 +605,21 @@ export const OpenMonetaGuard = async (ctx: GuardContext) => {
498
605
  ].join("\n"),
499
606
  )
500
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
+ }
501
623
  }
502
624
 
503
625
  if (isEditTool(tool)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmoneta-dev-kit",
3
- "version": "1.12.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).
@@ -42,14 +42,14 @@ Stale docs còn tệ hơn không có docs — session sau AI đọc sai, làm sa
42
42
 
43
43
  ## Workflow
44
44
 
45
- ### 1. Lấy danh sách (deterministic)
45
+ ### 1. Lấy danh sách + soi ghost (deterministic)
46
46
 
47
47
  ```bash
48
- bash ~/.cursor/scripts/docs-audit.sh --worklist
49
- # Output TSV: <slug>\t<docsPath>\t<sourcePath>\t<missing|stale|ok>
48
+ bash ~/.cursor/scripts/docs-audit.sh --worklist # MISSING/STALE/ok (TSV: slug\tdocs\tsource\tstatus)
49
+ bash ~/.cursor/scripts/docs-audit.sh --validate # liệt GHOST (doc/INDEX trỏ source/module đã mất)
50
50
  ```
51
51
 
52
- (OpenCode: `~/.config/opencode/scripts/docs-audit.sh`.) Mode `audit` chỉ in report rồi dừng, không qua các bước dưới.
52
+ (OpenCode: `~/.config/opencode/scripts/docs-audit.sh`.) **BẮT BUỘC chạy cả `--validate`** ở mọi mode để không bỏ sót ghost. Mode `audit` in cả 2 kết quả rồi dừng, không qua các bước tạo/sửa dưới.
53
53
 
54
54
  ### 2. (Repo lớn) đọc song song
55
55
 
@@ -75,7 +75,7 @@ Cho mỗi module status `stale`:
75
75
  ### 5. Sync `docs/INDEX.md`
76
76
 
77
77
  - Module mới → thêm dòng vào bảng "Modules hiện có" + **3-5 keyword (VN+EN)** vào bảng "Token Routing" (bắt buộc, để hook docs-first + token-aware reading hoạt động).
78
- - GHOST → báo user, đề xuất xóa dòng/thư mục (KHÔNG tự xóa nếu chưa chắc).
78
+ - **GHOST** (từ `--validate` ở Bước 1) LIỆT KÊ rõ từng ghost cho user. **KHÔNG tự xóa** — map slug→source là heuristic, có thể là module vừa đổi tên/di chuyển. Hỏi user bằng `AskQuestion` (xóa doc/dòng INDEX, hay map lại sang source mới); chỉ thao tác sau khi user xác nhận từng cái.
79
79
 
80
80
  ### 6. Report cuối
81
81
 
@@ -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).
@@ -57,6 +57,104 @@ function pruneRegistry() {
57
57
  return pruned
58
58
  }
59
59
 
60
+ const SCAN_PRUNE_DIRS = new Set([
61
+ "node_modules",
62
+ ".git",
63
+ "Library",
64
+ ".Trash",
65
+ ".cache",
66
+ ".npm",
67
+ ".cargo",
68
+ ".gradle",
69
+ ".venv",
70
+ "dist",
71
+ "build",
72
+ ])
73
+
74
+ function findProjects(root, maxDepth) {
75
+ const out = []
76
+ function walk(dir, depth) {
77
+ if (depth > maxDepth) return
78
+ if (isOpenmonetaProject(dir)) {
79
+ try {
80
+ fs.accessSync(path.join(dir, "AGENTS.md"))
81
+ out.push(dir)
82
+ } catch {
83
+ // thiếu AGENTS.md → không tính là OM project khi scan
84
+ }
85
+ }
86
+ let entries
87
+ try {
88
+ entries = fs.readdirSync(dir, { withFileTypes: true })
89
+ } catch {
90
+ return
91
+ }
92
+ for (const e of entries) {
93
+ if (!e.isDirectory()) continue
94
+ if (SCAN_PRUNE_DIRS.has(e.name) || e.name.startsWith(".")) continue
95
+ walk(path.join(dir, e.name), depth + 1)
96
+ }
97
+ }
98
+ walk(root, 0)
99
+ return out
100
+ }
101
+
102
+ function scanSeed(root, pkgRoot) {
103
+ let absRoot
104
+ try {
105
+ absRoot = fs.realpathSync(root)
106
+ } catch {
107
+ console.log(` [!] Scan root không tồn tại: ${root}`)
108
+ return
109
+ }
110
+ const targets = ["cursor", "opencode"].filter((t) => isInstalled(t))
111
+ if (targets.length === 0) {
112
+ console.log(` [!] Chưa có install home nào để seed registry.`)
113
+ return
114
+ }
115
+ let kitRoot
116
+ try {
117
+ kitRoot = fs.realpathSync(pkgRoot)
118
+ } catch {
119
+ kitRoot = pkgRoot
120
+ }
121
+ const uniq = [
122
+ ...new Set(
123
+ findProjects(absRoot, 5)
124
+ .map((p) => {
125
+ try {
126
+ return fs.realpathSync(p)
127
+ } catch {
128
+ return p
129
+ }
130
+ })
131
+ .filter((p) => p !== kitRoot)
132
+ ),
133
+ ]
134
+ let seeded = 0
135
+ for (const t of targets) {
136
+ const reg = getProjectRegistry(t)
137
+ let existing = new Set()
138
+ try {
139
+ existing = new Set(
140
+ fs
141
+ .readFileSync(reg, "utf8")
142
+ .split("\n")
143
+ .map((l) => l.trim())
144
+ .filter(Boolean)
145
+ )
146
+ } catch {
147
+ // registry chưa tồn tại → tạo mới khi append
148
+ }
149
+ const toAdd = uniq.filter((p) => !existing.has(p))
150
+ if (toAdd.length) {
151
+ fs.appendFileSync(reg, toAdd.map((p) => `${p}\n`).join(""))
152
+ seeded += toAdd.length
153
+ }
154
+ }
155
+ console.log(` Scan: thấy ${uniq.length} OM project, seed ${seeded} entry mới.`)
156
+ }
157
+
60
158
  function syncProject(initScript, dir) {
61
159
  if (isWindows()) {
62
160
  execSync(`bash "${initScript}" "${dir}"`, { stdio: "inherit", shell: true })
@@ -69,7 +167,14 @@ async function run(args) {
69
167
  const checkOnly = args.includes("--check")
70
168
  const autoYes = args.includes("--yes") || args.includes("-y")
71
169
  const skipProjects = args.includes("--skip-projects")
72
- const allProjects = args.includes("--all-projects")
170
+ const scanIdx = args.indexOf("--scan")
171
+ const scan = scanIdx !== -1
172
+ let scanRoot = process.env.HOME || process.env.USERPROFILE || "."
173
+ if (scan) {
174
+ const next = args[scanIdx + 1]
175
+ if (next && !next.startsWith("-")) scanRoot = next
176
+ }
177
+ const allProjects = args.includes("--all-projects") || scan
73
178
  const force = args.includes("--force")
74
179
 
75
180
  const local = getLocalVersion()
@@ -140,6 +245,11 @@ async function run(args) {
140
245
  const initScript = path.join(pkgRoot, "scripts", "init-project.sh")
141
246
  const cwd = process.cwd()
142
247
 
248
+ if (scan) {
249
+ console.log(`\n ▶ --scan: dò OM project để seed registry...`)
250
+ scanSeed(scanRoot, pkgRoot)
251
+ }
252
+
143
253
  if (allProjects) {
144
254
  const pruned = pruneRegistry()
145
255
  if (pruned > 0) console.log(`\n ℹ Registry: prune ${pruned} entry chết.`)
@@ -196,4 +306,7 @@ async function run(args) {
196
306
  console.log(`\n 🎉 Hoàn tất. Restart Cursor/OpenCode để áp dụng.`)
197
307
  }
198
308
 
199
- module.exports = { run, _internal: { isOpenmonetaProject, readRegistry, pruneRegistry } }
309
+ module.exports = {
310
+ run,
311
+ _internal: { isOpenmonetaProject, readRegistry, pruneRegistry, findProjects, scanSeed },
312
+ }
@@ -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
package/update.ps1 CHANGED
@@ -7,6 +7,7 @@
7
7
  # .\update.ps1 -Check # chỉ check, không cài
8
8
  # .\update.ps1 -Project C:\path # global + sync thêm project chỉ định
9
9
  # .\update.ps1 -AllProjects # global + sync MỌI project trong registry
10
+ # .\update.ps1 -Scan [-ScanRoot path] # dò OM project (mặc định $HOME) → seed registry → sync all
10
11
  # .\update.ps1 -SkipProjects # global only (old behavior)
11
12
 
12
13
  param(
@@ -15,6 +16,8 @@ param(
15
16
  [switch]$Check,
16
17
  [switch]$SkipProjects,
17
18
  [switch]$AllProjects,
19
+ [switch]$Scan,
20
+ [string]$ScanRoot,
18
21
  [string[]]$Project
19
22
  )
20
23
 
@@ -124,6 +127,49 @@ function Invoke-PruneRegistry {
124
127
  return $pruned
125
128
  }
126
129
 
130
+ # Helper: scan tìm OM project (docs\INDEX.md + AGENTS.md) → seed registry mỗi home tồn tại
131
+ function Invoke-ScanSeed {
132
+ $root = if ($ScanRoot) { $ScanRoot } else { $env:USERPROFILE }
133
+ $absRoot = (Resolve-Path $root -ErrorAction SilentlyContinue).Path
134
+ if (-not $absRoot) { Write-Host " [!] Scan root không tồn tại: $root"; return }
135
+
136
+ $homes = @()
137
+ if (Test-Path (Join-Path $env:USERPROFILE ".cursor\.openmoneta-version")) {
138
+ $homes += (Join-Path $env:USERPROFILE ".cursor\.openmoneta-projects")
139
+ }
140
+ $ocBase = if ($env:XDG_CONFIG_HOME) { "$env:XDG_CONFIG_HOME\opencode" } else { "$env:USERPROFILE\.config\opencode" }
141
+ if (Test-Path (Join-Path $ocBase ".openmoneta-version")) {
142
+ $homes += (Join-Path $ocBase ".openmoneta-projects")
143
+ }
144
+ if ($homes.Count -eq 0) { Write-Host " [!] Chưa có install home nào để seed registry."; return }
145
+
146
+ $prune = @('node_modules', '.git', 'Library', '.Trash', '.cache', '.npm', '.cargo', '.gradle', '.venv', 'dist', 'build')
147
+ Write-Host " Scanning '$absRoot' (depth ~6)..."
148
+ $indexes = Get-ChildItem -Path $absRoot -Recurse -Depth 6 -Filter "INDEX.md" -File -ErrorAction SilentlyContinue |
149
+ Where-Object { (Split-Path $_.DirectoryName -Leaf) -eq "docs" }
150
+
151
+ $found = 0; $seeded = 0
152
+ $absKit = (Resolve-Path $RepoDir -ErrorAction SilentlyContinue).Path
153
+ foreach ($idx in $indexes) {
154
+ $proj = Split-Path (Split-Path $idx.FullName -Parent) -Parent
155
+ $skip = $false
156
+ foreach ($d in $prune) { if ($proj -like "*\$d\*" -or $proj -like "*\$d") { $skip = $true; break } }
157
+ if ($skip) { continue }
158
+ if (-not (Test-Path (Join-Path $proj "AGENTS.md"))) { continue }
159
+ $abs = (Resolve-Path $proj -ErrorAction SilentlyContinue).Path
160
+ if (-not $abs -or $abs -eq $absKit) { continue }
161
+ $found++
162
+ foreach ($reg in $homes) {
163
+ $existing = @()
164
+ if (Test-Path $reg) { $existing = Get-Content $reg | ForEach-Object { $_.Trim() } | Where-Object { $_ } }
165
+ if ($existing -contains $abs) { continue }
166
+ Add-Content -Path $reg -Value $abs
167
+ $seeded++
168
+ }
169
+ }
170
+ Write-Host " Scan: thấy $found OM project, seed $seeded entry mới vào registry."
171
+ }
172
+
127
173
  # Helper: collect projects to sync
128
174
  function Get-OpenMonetaProjects {
129
175
  $found = @()
@@ -262,6 +308,13 @@ if ($SkipProjects) {
262
308
  exit 0
263
309
  }
264
310
 
311
+ if ($Scan) {
312
+ $AllProjects = $true
313
+ Write-Host ""
314
+ Write-Host "==> -Scan: dò OM project để seed registry..."
315
+ Invoke-ScanSeed
316
+ }
317
+
265
318
  if ($AllProjects) {
266
319
  Write-Host ""
267
320
  Write-Host "==> -AllProjects: dọn registry + gom mọi project đã đăng ký..."
package/update.sh CHANGED
@@ -9,6 +9,7 @@
9
9
  # bash update.sh --check # chỉ check, không cài
10
10
  # bash update.sh --project /path # global + sync thêm project chỉ định
11
11
  # bash update.sh --all-projects # global + sync MỌI project trong registry
12
+ # bash update.sh --scan [root] # dò OM project (mặc định $HOME) → seed registry → sync all
12
13
  # bash update.sh --skip-projects # global only (old behavior)
13
14
 
14
15
  set -euo pipefail
@@ -22,31 +23,42 @@ SKIP_PROJECTS=0
22
23
  FORCE=0
23
24
  CHECK_ONLY=0
24
25
  ALL_PROJECTS=0
26
+ SCAN=0
27
+ SCAN_ROOT=""
25
28
  PROJECT_PATHS=()
26
29
 
27
- for arg in "$@"; do
28
- case "$arg" in
30
+ while [[ $# -gt 0 ]]; do
31
+ case "$1" in
29
32
  --yes|-y) AUTO_YES=1 ;;
30
33
  --force) FORCE=1 ;;
31
34
  --check) CHECK_ONLY=1 ;;
32
35
  --all-projects) ALL_PROJECTS=1 ;;
33
36
  --skip-projects) SKIP_PROJECTS=1 ;;
37
+ --scan)
38
+ SCAN=1
39
+ ALL_PROJECTS=1
40
+ if [[ -n "${2:-}" && "${2:-}" != -* ]]; then
41
+ SCAN_ROOT="$2"
42
+ shift
43
+ fi
44
+ ;;
34
45
  --help|-h)
35
- grep '^#' "$0" | sed 's/^# \?//' | head -17
46
+ grep '^#' "$0" | sed 's/^# \?//' | head -18
36
47
  exit 0
37
48
  ;;
38
49
  --project)
39
- shift
40
- if [[ -n "${1:-}" && "${1:-}" != -* ]]; then
41
- PROJECT_PATHS+=("$1")
50
+ if [[ -n "${2:-}" && "${2:-}" != -* ]]; then
51
+ PROJECT_PATHS+=("$2")
52
+ shift
42
53
  fi
43
54
  ;;
44
55
  *)
45
- if [[ "$arg" != -* ]]; then
46
- PROJECT_PATHS+=("$arg")
56
+ if [[ "$1" != -* ]]; then
57
+ PROJECT_PATHS+=("$1")
47
58
  fi
48
59
  ;;
49
60
  esac
61
+ shift
50
62
  done
51
63
 
52
64
  cd "$REPO_DIR"
@@ -146,6 +158,45 @@ prune_registry() {
146
158
  return 0
147
159
  }
148
160
 
161
+ # === Helper: scan tìm OM project (docs/INDEX.md + AGENTS.md) → seed registry mỗi home tồn tại ===
162
+ scan_seed() {
163
+ local root="${SCAN_ROOT:-$HOME}"
164
+ local abs_root
165
+ abs_root="$(cd "$root" 2>/dev/null && pwd -P)" || { echo " [!] Scan root không tồn tại: $root"; return 0; }
166
+
167
+ local homes=()
168
+ [[ -f "$HOME/.cursor/.openmoneta-version" ]] && homes+=("$HOME/.cursor/.openmoneta-projects")
169
+ [[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/opencode/.openmoneta-version" ]] && \
170
+ homes+=("${XDG_CONFIG_HOME:-$HOME/.config}/opencode/.openmoneta-projects")
171
+ if [[ ${#homes[@]} -eq 0 ]]; then
172
+ echo " [!] Chưa có install home nào để seed registry."
173
+ return 0
174
+ fi
175
+
176
+ echo " Scanning '$abs_root' (maxdepth project ~5)..."
177
+ local found=0 seeded=0 idx proj abs reg
178
+ while IFS= read -r idx; do
179
+ proj="$(dirname "$(dirname "$idx")")"
180
+ [[ -f "$proj/AGENTS.md" ]] || continue
181
+ abs="$(cd "$proj" 2>/dev/null && pwd -P)" || continue
182
+ [[ -z "$abs" || "$abs" == "$REPO_DIR" ]] && continue
183
+ found=$((found + 1))
184
+ for reg in "${homes[@]}"; do
185
+ if [[ -f "$reg" ]] && grep -qFx "$abs" "$reg" 2>/dev/null; then
186
+ continue
187
+ fi
188
+ echo "$abs" >> "$reg"
189
+ seeded=$((seeded + 1))
190
+ done
191
+ done < <(find "$abs_root" -maxdepth 7 \
192
+ \( -name node_modules -o -name .git -o -name Library -o -name .Trash \
193
+ -o -name .cache -o -name .npm -o -name .cargo -o -name .gradle \
194
+ -o -name .venv -o -name dist -o -name build \) -prune -o \
195
+ -type f -path '*/docs/INDEX.md' -print 2>/dev/null)
196
+
197
+ echo " Scan: thấy $found OM project, seed $seeded entry mới vào registry."
198
+ }
199
+
149
200
  # === Helper: collect projects to sync ===
150
201
  collect_projects() {
151
202
  local found=()
@@ -293,6 +344,12 @@ print_registry_hint() {
293
344
  return 0
294
345
  }
295
346
 
347
+ if [[ $SCAN -eq 1 ]]; then
348
+ echo ""
349
+ echo "==> --scan: dò OM project để seed registry..."
350
+ scan_seed
351
+ fi
352
+
296
353
  if [[ $ALL_PROJECTS -eq 1 ]]; then
297
354
  echo ""
298
355
  echo "==> --all-projects: dọn registry + gom mọi project đã đăng ký..."