openmoneta-dev-kit 1.11.1 → 1.12.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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Biến **Cursor IDE / OpenCode** thành một **team developer hoàn chỉnh** với quy trình 6 bước (Lean Mode), adaptive planning, hooks enforcement, token-aware doc routing và sub-agents on-demand.
4
4
 
5
- [![version](https://img.shields.io/badge/version-1.9.0-blue.svg)](./VERSION)
5
+ [![version](https://img.shields.io/badge/version-1.12.0-blue.svg)](./VERSION)
6
6
  [![docs](https://img.shields.io/badge/docs-Vietnamese-green.svg)](./docs/INDEX.md)
7
7
  [![platform](https://img.shields.io/badge/platform-macOS%20|%20Linux%20|%20Windows-lightgrey.svg)]()
8
8
  [![npm](https://img.shields.io/npm/v/openmoneta-dev-kit)](https://www.npmjs.com/package/openmoneta-dev-kit)
@@ -41,6 +41,7 @@ openmoneta init
41
41
  openmoneta update # global + sync project hiện tại
42
42
  openmoneta update --yes # auto-sync, skip nếu đã latest
43
43
  openmoneta check # chỉ kiểm tra version mới
44
+ openmoneta docs # audit module README thiếu/lỗi thời + sync INDEX
44
45
  ```
45
46
 
46
47
  ## Cài đặt thủ công (git clone)
@@ -54,9 +55,10 @@ bash ~/OpenMoneta-Dev-Kit/install.sh
54
55
 
55
56
  ## Có gì trong này?
56
57
 
57
- - **10 skills** (4 core + 1 core conditional + 5 on-demand) — phân tích yêu cầu, thiết kế module, plan, systematic debugging, safe push, test, security
58
+ - **11 skills** (4 core + 1 core conditional + 6 on-demand) — phân tích yêu cầu, thiết kế module, plan, systematic debugging, safe push, test, security, bảo trì docs
59
+ - **`openmoneta docs`** — audit module README thiếu/lỗi thời (stale) + skill `docs-maintenance` backfill docs cho dự án cũ
58
60
  - **4 sub-agents** — 1 core + 3 on-demand (security-auditor, qa-autonomous, ui-tester)
59
- - **4 hooks + plugin** — enforce token-aware reading, adaptive plan scope
61
+ - **4 hooks + plugin** — enforce token-aware reading, adaptive plan scope, và verify cuối phiên (module README + close plan); OpenCode đạt parity qua plugin guard `session.idle`
60
62
  - **Token Routing** — bảng map keyword → module giúp AI giảm 70-90% token đọc
61
63
  - **npm package** — `npm install -g openmoneta-dev-kit`, không cần git, không cần clone
62
64
  - **Auto-update check** — notify khi có version mới (cache 24h)
package/VERSION CHANGED
@@ -1 +1 @@
1
- 1.11.1
1
+ 1.12.0
@@ -44,7 +44,7 @@ SUMMARY='# OpenMoneta Dev Kit v1.7.0 — Quy trình 6 bước (Adaptive Planning
44
44
  - `security-auditor`, `qa-autonomous`, `ui-tester` — **ON-DEMAND ONLY** (chỉ khi user yêu cầu explicit).
45
45
 
46
46
  ## Skills on-demand (KHÔNG tự trigger)
47
- `security-checklist`, `test-strategy`, `automated-testing`, `auth-bypass-testing`, `ui-test-loop` — chỉ kích hoạt khi user yêu cầu rõ ("audit security X", "viết test cho Y", "Playwright UI test Z").
47
+ `security-checklist`, `test-strategy`, `automated-testing`, `auth-bypass-testing`, `ui-test-loop`, `docs-maintenance` — chỉ kích hoạt khi user yêu cầu rõ ("audit security X", "viết test cho Y", "Playwright UI test Z", "tạo/bảo trì docs dự án cũ").
48
48
 
49
49
  ## Skill core conditional
50
50
  `safe-push` — bắt buộc khi user yêu cầu push/đẩy code để tránh đè code người khác trong repo nhiều contributor.
@@ -29,6 +29,7 @@
29
29
  - OpenCode đọc global rules từ `~/.config/opencode/AGENTS.md`.
30
30
  - OpenCode đọc project rules từ `AGENTS.md` ở project root. Project rules là source of truth cho từng repo.
31
31
  - 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
+ - **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`).
32
33
  - OpenMoneta subagents được cài vào `~/.config/opencode/agents/`.
33
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).
34
35
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmoneta-dev-kit",
3
- "version": "1.11.1",
3
+ "version": "1.12.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",
@@ -0,0 +1,185 @@
1
+ #!/usr/bin/env bash
2
+ # docs-audit.sh — Quét module trong project hiện tại và đối chiếu với docs/modules/<slug>/README.md.
3
+ #
4
+ # Mục đích: phát hiện module thiếu doc (MISSING), doc lỗi thời so với code (STALE),
5
+ # và doc/INDEX trỏ tới module không còn tồn tại (GHOST). Deterministic, không sửa file.
6
+ #
7
+ # Usage (chạy ở thư mục gốc project):
8
+ # bash docs-audit.sh # report bảng (mặc định)
9
+ # bash docs-audit.sh --report # như trên
10
+ # bash docs-audit.sh --worklist # TSV: <slug>\t<docsPath>\t<sourcePath>\t<missing|stale|ok>
11
+ # bash docs-audit.sh --validate # liệt kê GHOST (doc trỏ source/module không tồn tại)
12
+ #
13
+ # Stale detection: so commit mới nhất của source vs README (git); fallback mtime filesystem.
14
+
15
+ set -uo pipefail
16
+
17
+ MODE="report"
18
+ case "${1:-}" in
19
+ --worklist) MODE="worklist" ;;
20
+ --validate) MODE="validate" ;;
21
+ --report|"") MODE="report" ;;
22
+ *) echo "[!] Mode không hợp lệ: $1 (dùng --report|--worklist|--validate)" >&2; exit 2 ;;
23
+ esac
24
+
25
+ GIT_OK=0
26
+ if command -v git >/dev/null 2>&1 && git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
27
+ GIT_OK=1
28
+ fi
29
+
30
+ file_mtime() { stat -c %Y "$1" 2>/dev/null || stat -f %m "$1" 2>/dev/null || echo 0; }
31
+
32
+ newest_mtime() {
33
+ local target="$1" newest=0 m
34
+ while IFS= read -r f; do
35
+ m=$(file_mtime "$f")
36
+ [[ "$m" -gt "$newest" ]] && newest=$m
37
+ done < <(find "$target" -type f 2>/dev/null)
38
+ echo "$newest"
39
+ }
40
+
41
+ git_last_commit() { git log -1 --format=%ct -- "$1" 2>/dev/null || echo ""; }
42
+
43
+ # In status missing|stale|ok cho 1 module
44
+ status_for() {
45
+ local readme="$1" src="$2" tr ts
46
+ if [[ ! -f "$readme" ]]; then echo "missing"; return; fi
47
+ if [[ "$GIT_OK" -eq 1 ]]; then
48
+ tr=$(git_last_commit "$readme")
49
+ ts=$(git_last_commit "$src")
50
+ fi
51
+ if [[ -z "${tr:-}" || -z "${ts:-}" ]]; then
52
+ tr=$(file_mtime "$readme")
53
+ ts=$(newest_mtime "$src")
54
+ fi
55
+ if [[ "${ts:-0}" -gt "${tr:-0}" ]]; then echo "stale"; else echo "ok"; fi
56
+ }
57
+
58
+ # Liệt kê module trong filesystem → "<slug>|<docsPath>|<sourcePath>"
59
+ collect_modules() {
60
+ local d n e
61
+ if [[ -d apps ]]; then
62
+ for d in apps/*/; do
63
+ [[ -d "$d" ]] || continue; n=$(basename "$d"); [[ "$n" == .* ]] && continue
64
+ echo "apps-$n|docs/modules/apps-$n|apps/$n"
65
+ done
66
+ fi
67
+ if [[ -d packages ]]; then
68
+ for d in packages/*/; do
69
+ [[ -d "$d" ]] || continue; n=$(basename "$d"); [[ "$n" == .* ]] && continue
70
+ echo "packages-$n|docs/modules/packages-$n|packages/$n"
71
+ done
72
+ fi
73
+ if [[ -d src/modules ]]; then
74
+ for d in src/modules/*/; do
75
+ [[ -d "$d" ]] || continue; n=$(basename "$d"); [[ "$n" == .* ]] && continue
76
+ echo "$n|docs/modules/$n|src/modules/$n"
77
+ done
78
+ elif [[ -d src ]]; then
79
+ for e in src/*; do
80
+ [[ -e "$e" ]] || continue; n=$(basename "$e")
81
+ [[ "$n" == .* ]] && continue
82
+ [[ "$n" == "modules" ]] && continue
83
+ echo "$n|docs/modules/$n|$e"
84
+ done
85
+ fi
86
+ }
87
+
88
+ in_index() {
89
+ [[ -f docs/INDEX.md ]] || { echo "no"; return; }
90
+ if grep -q "modules/$1" docs/INDEX.md 2>/dev/null; then echo "yes"; else echo "no"; fi
91
+ }
92
+
93
+ # Map slug docs → candidate source path (cho validate/ghost)
94
+ slug_to_source() {
95
+ local slug="$1"
96
+ if [[ "$slug" == apps-* ]]; then echo "apps/${slug#apps-}"
97
+ elif [[ "$slug" == packages-* ]]; then echo "packages/${slug#packages-}"
98
+ elif [[ -e "src/modules/$slug" ]]; then echo "src/modules/$slug"
99
+ else echo "src/$slug"
100
+ fi
101
+ }
102
+
103
+ MODULES=$(collect_modules | sort -u)
104
+
105
+ # ---------- MODE: validate (ghost) ----------
106
+ if [[ "$MODE" == "validate" ]]; then
107
+ ghosts=0
108
+ if [[ -d docs/modules ]]; then
109
+ for d in docs/modules/*/; do
110
+ [[ -d "$d" ]] || continue
111
+ slug=$(basename "$d")
112
+ src=$(slug_to_source "$slug")
113
+ if [[ ! -e "$src" ]]; then
114
+ echo "GHOST docs/modules/$slug → source '$src' không tồn tại"
115
+ ghosts=$((ghosts+1))
116
+ fi
117
+ done
118
+ fi
119
+ # INDEX trỏ tới docs/modules/<x> không tồn tại
120
+ if [[ -f docs/INDEX.md ]]; then
121
+ while IFS= read -r ref; do
122
+ slug="${ref#modules/}"; slug="${slug%%/*}"
123
+ [[ -z "$slug" ]] && continue
124
+ if [[ ! -d "docs/modules/$slug" ]]; then
125
+ echo "GHOST docs/INDEX.md → 'modules/$slug' nhưng thư mục docs/modules/$slug không tồn tại"
126
+ ghosts=$((ghosts+1))
127
+ fi
128
+ done < <(grep -oE 'modules/[A-Za-z0-9._-]+' docs/INDEX.md 2>/dev/null | sort -u)
129
+ fi
130
+ if [[ "$ghosts" -eq 0 ]]; then
131
+ echo "✓ Không phát hiện GHOST doc (mọi docs/modules + tham chiếu INDEX đều có source)."
132
+ else
133
+ echo ""
134
+ echo "Tổng: $ghosts cảnh báo GHOST."
135
+ fi
136
+ exit 0
137
+ fi
138
+
139
+ # ---------- MODE: worklist (TSV cho skill) ----------
140
+ if [[ "$MODE" == "worklist" ]]; then
141
+ [[ -z "$MODULES" ]] && exit 0
142
+ while IFS='|' read -r slug docs src; do
143
+ [[ -z "$slug" ]] && continue
144
+ st=$(status_for "$docs/README.md" "$src")
145
+ printf '%s\t%s\t%s\t%s\n' "$slug" "$docs" "$src" "$st"
146
+ done <<< "$MODULES"
147
+ exit 0
148
+ fi
149
+
150
+ # ---------- MODE: report (mặc định) ----------
151
+ echo ""
152
+ echo " Module doc audit — $(pwd)"
153
+ echo " ──────────────────────────────────────────────────────────────"
154
+ if [[ -z "$MODULES" ]]; then
155
+ echo " 0 module (không thấy apps/ packages/ src/). Không có gì để audit."
156
+ echo ""
157
+ exit 0
158
+ fi
159
+
160
+ printf " %-22s %-22s %-8s %-7s %-8s\n" "slug" "source" "README" "INDEX" "status"
161
+ printf " %-22s %-22s %-8s %-7s %-8s\n" "----" "------" "------" "-----" "------"
162
+
163
+ n_missing=0; n_stale=0; n_ok=0; n_noindex=0
164
+ while IFS='|' read -r slug docs src; do
165
+ [[ -z "$slug" ]] && continue
166
+ st=$(status_for "$docs/README.md" "$src")
167
+ idx=$(in_index "$slug")
168
+ rd="ok"; [[ "$st" == "missing" ]] && rd="MISSING"
169
+ printf " %-22s %-22s %-8s %-7s %-8s\n" "$slug" "$src" "$rd" "$idx" "$st"
170
+ case "$st" in
171
+ missing) n_missing=$((n_missing+1)) ;;
172
+ stale) n_stale=$((n_stale+1)) ;;
173
+ ok) n_ok=$((n_ok+1)) ;;
174
+ esac
175
+ [[ "$idx" == "no" ]] && n_noindex=$((n_noindex+1))
176
+ done <<< "$MODULES"
177
+
178
+ echo " ──────────────────────────────────────────────────────────────"
179
+ echo " Tổng: $((n_missing+n_stale+n_ok)) module · $n_missing thiếu README · $n_stale lỗi thời (stale) · $n_noindex chưa có trong INDEX"
180
+ if [[ "$n_missing" -gt 0 || "$n_stale" -gt 0 ]]; then
181
+ echo ""
182
+ echo " → Để SINH/CẬP NHẬT nội dung: mở Cursor/OpenCode và yêu cầu chạy skill 'docs-maintenance'."
183
+ fi
184
+ echo ""
185
+ exit 0
@@ -398,6 +398,25 @@ else
398
398
  fi
399
399
  fi
400
400
 
401
+ # === Bước 4: Đăng ký project vào registry của mỗi install home đang tồn tại ===
402
+ # Registry gắn với install home CỐ ĐỊNH (qua marker .openmoneta-version), KHÔNG dùng
403
+ # $OPENMONETA_HOME (vì khi chạy từ repo kit, biến này trỏ về repo dir).
404
+ register_in_home() {
405
+ local home="$1"
406
+ [[ -f "$home/.openmoneta-version" ]] || return 0
407
+ local reg="$home/.openmoneta-projects"
408
+ local abs
409
+ abs="$(cd "$PROJECT_DIR" && pwd -P)"
410
+ if [[ -f "$reg" ]] && grep -qFx "$abs" "$reg"; then
411
+ return 0
412
+ fi
413
+ echo "$abs" >> "$reg"
414
+ CREATED+=("registry: $reg ← $abs")
415
+ }
416
+
417
+ register_in_home "$HOME/.cursor"
418
+ register_in_home "${XDG_CONFIG_HOME:-$HOME/.config}/opencode"
419
+
401
420
  # === Báo cáo ===
402
421
  echo ""
403
422
  echo "================================================"
@@ -0,0 +1,106 @@
1
+ ---
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."
4
+ ---
5
+
6
+ # Docs Maintenance
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).
9
+
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
+
12
+ ## Iron Law
13
+
14
+ ```
15
+ CHỈ VIẾT NHỮNG GÌ VERIFY ĐƯỢC TRONG CODE. KHÔNG BỊA API / PATH / DEPENDENCY.
16
+ ```
17
+
18
+ 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
+
20
+ ## Ranh giới (Lean — KHÔNG làm gì)
21
+
22
+ - **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.
23
+ - **KHÔNG** sửa code. Chỉ ghi file dưới `docs/`.
24
+ - **KHÔNG** viết lại từ đầu README còn đúng — chỉ sửa phần lệch.
25
+ - Cấu trúc README 3 sections theo skill [`module-architect`](../module-architect/SKILL.md) — KHÔNG lặp lại spec ở đây, đọc nó để biết format chuẩn.
26
+
27
+ ## 3 Modes
28
+
29
+ | Mode | Xử lý | Ghi file? |
30
+ |---|---|---|
31
+ | `audit` | Report: liệt kê module MISSING / STALE / GHOST | Không |
32
+ | `init` | **Tạo MISSING + refresh STALE** (full backfill) | Có |
33
+ | `sync` | Chỉ refresh STALE | Có |
34
+
35
+ > User nói "tạo docs thiếu" / "bảo trì docs cũ" → mặc định `init`. Nói "chỉ xem thiếu gì" → `audit`. Nói "cập nhật doc cho khớp code" → `sync`.
36
+
37
+ ## Phân loại trạng thái module
38
+
39
+ - **MISSING**: module có source nhưng KHÔNG có `docs/modules/<slug>/README.md`.
40
+ - **STALE**: README có rồi nhưng code module đổi SAU lần doc update cuối (so commit git source vs README; fallback mtime). Heuristic → phải đọc xác nhận trước khi sửa.
41
+ - **GHOST**: `docs/modules/<slug>/` hoặc dòng INDEX trỏ tới source/module không còn tồn tại.
42
+
43
+ ## Workflow
44
+
45
+ ### 1. Lấy danh sách (deterministic)
46
+
47
+ ```bash
48
+ bash ~/.cursor/scripts/docs-audit.sh --worklist
49
+ # Output TSV: <slug>\t<docsPath>\t<sourcePath>\t<missing|stale|ok>
50
+ ```
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.
53
+
54
+ ### 2. (Repo lớn) đọc song song
55
+
56
+ Nếu nhiều module MISSING/STALE → delegate subagent `explore` đọc song song từng cụm module và trả summary (Public API thật + imports). Repo nhỏ → đọc trực tiếp.
57
+
58
+ ### 3. MISSING → tạo mới
59
+
60
+ Cho mỗi module status `missing`:
61
+
62
+ 1. Đọc source thật (`sourcePath`) — entry point + các export.
63
+ 2. **Verify trước khi viết**: grep tên hàm/class/route trong source để chắc nó tồn tại + đúng signature.
64
+ 3. Viết `docs/modules/<slug>/README.md` 3 sections (Trách nhiệm + Public API + Dependencies) theo `module-architect`.
65
+
66
+ ### 4. STALE → refresh (không viết lại từ đầu)
67
+
68
+ Cho mỗi module status `stale`:
69
+
70
+ 1. Đọc source + README cũ.
71
+ 2. Đối chiếu **Public API**: export mới chưa có trong README → thêm; symbol đã xóa/đổi signature → sửa/xóa.
72
+ 3. Đối chiếu **Dependencies**: import mới → thêm; import đã bỏ → xóa.
73
+ 4. Giữ nguyên phần còn đúng. Chỉ sửa phần lệch.
74
+
75
+ ### 5. Sync `docs/INDEX.md`
76
+
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).
79
+
80
+ ### 6. Report cuối
81
+
82
+ 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
+
84
+ ## Bảng Rationalization (cái cớ → sự thật)
85
+
86
+ | Cái cớ | Sự thật |
87
+ |---|---|
88
+ | "README cũ chắc vẫn đúng, khỏi đọc source" | Stale là lý do bạn được gọi. Phải đọc source verify, không tin README cũ. |
89
+ | "Đoán signature cho nhanh" | Bịa API = doc sai = tệ hơn không có. Grep verify hoặc bỏ. |
90
+ | "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
+ | "Thêm doc tổng quan cho đầy đủ" | Trái Lean. OM chỉ module README + INDEX. |
92
+ | "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. |
93
+
94
+ ## Red Flags — DỪNG
95
+
96
+ - Đang định viết `funcName()` mà chưa thấy nó trong source.
97
+ - Đang refresh STALE mà chưa mở source, chỉ sửa theo trí nhớ.
98
+ - Đang định sinh `system-architecture.md` / `codebase-summary.md`.
99
+ - Đang định xóa GHOST mà chưa hỏi user.
100
+ - Tạo module mới trong INDEX nhưng quên Token Routing keyword.
101
+
102
+ ## Examples
103
+
104
+ - 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
+ - User: "Doc còn khớp code không?" → `audit`: chỉ report.
106
+ - User: "Cập nhật doc 3 module vừa refactor" → `sync` (giới hạn các slug đó).
package/src/cli.js CHANGED
@@ -19,6 +19,8 @@ async function cli(pkgRoot) {
19
19
  case "check":
20
20
  case "--check":
21
21
  return require("./commands/check").run(args)
22
+ case "docs":
23
+ return require("./commands/docs").run(args)
22
24
  case "--version":
23
25
  case "-v":
24
26
  console.log(`v${getLocalVersion() || "unknown"}`)
@@ -52,6 +54,9 @@ Biến Cursor IDE / OpenCode thành team developer hoàn chỉnh.
52
54
 
53
55
  openmoneta check Kiểm tra version mới nhất từ npm
54
56
 
57
+ openmoneta docs Audit module README (thiếu/lỗi thời) + sync INDEX
58
+ openmoneta docs --validate Phát hiện doc trỏ module/source không tồn tại (ghost)
59
+
55
60
  openmoneta uninstall Gỡ cài đặt
56
61
 
57
62
  Trạng thái: Cursor: ${isInstalled("cursor") ? "đã cài" : "chưa cài"} | OpenCode: ${isInstalled("opencode") ? "đã cài" : "chưa cài"}
@@ -0,0 +1,37 @@
1
+ const { execSync } = require("node:child_process")
2
+ const path = require("node:path")
3
+ const fs = require("node:fs")
4
+ const { getPkgRoot, isWindows } = require("../lib/paths")
5
+
6
+ async function run(args) {
7
+ const validate = args.includes("--validate")
8
+ const worklist = args.includes("--worklist")
9
+
10
+ const pkgRoot = getPkgRoot()
11
+ const script = path.join(pkgRoot, "scripts", "docs-audit.sh")
12
+ const cwd = process.cwd()
13
+
14
+ if (!fs.existsSync(script)) {
15
+ console.error(`\n ❌ Không tìm thấy script: ${script}`)
16
+ process.exit(1)
17
+ }
18
+
19
+ let mode = "--report"
20
+ if (validate) mode = "--validate"
21
+ else if (worklist) mode = "--worklist"
22
+
23
+ try {
24
+ execSync(`bash "${script}" ${mode}`, {
25
+ stdio: "inherit",
26
+ cwd,
27
+ shell: isWindows() ? true : undefined,
28
+ })
29
+ } catch (err) {
30
+ if (isWindows()) {
31
+ console.log(`\n ⚠ Cần Git Bash để chạy audit trên Windows: https://git-scm.com/download/win`)
32
+ }
33
+ process.exit(typeof err.status === "number" ? err.status : 1)
34
+ }
35
+ }
36
+
37
+ module.exports = { run }
@@ -1,12 +1,75 @@
1
1
  const { execSync } = require("node:child_process")
2
2
  const path = require("node:path")
3
- const { getPkgRoot, isInstalled, isWindows, runScript } = require("../lib/paths")
3
+ const fs = require("node:fs")
4
+ const { getPkgRoot, isInstalled, isWindows, runScript, getProjectRegistry } = require("../lib/paths")
4
5
  const { getLocalVersion, getLatestVersion } = require("../lib/version")
5
6
 
7
+ function isOpenmonetaProject(dir) {
8
+ try {
9
+ fs.accessSync(path.join(dir, "docs", "INDEX.md"))
10
+ return true
11
+ } catch {
12
+ return false
13
+ }
14
+ }
15
+
16
+ function readRegistry() {
17
+ const seen = new Set()
18
+ for (const target of ["cursor", "opencode"]) {
19
+ let content
20
+ try {
21
+ content = fs.readFileSync(getProjectRegistry(target), "utf8")
22
+ } catch {
23
+ continue
24
+ }
25
+ for (const line of content.split("\n")) {
26
+ const p = line.trim()
27
+ if (!p) continue
28
+ try {
29
+ seen.add(fs.realpathSync(p))
30
+ } catch {
31
+ // path không còn tồn tại → bỏ qua, prune sẽ dọn
32
+ }
33
+ }
34
+ }
35
+ return [...seen]
36
+ }
37
+
38
+ function pruneRegistry() {
39
+ let pruned = 0
40
+ for (const target of ["cursor", "opencode"]) {
41
+ const reg = getProjectRegistry(target)
42
+ let content
43
+ try {
44
+ content = fs.readFileSync(reg, "utf8")
45
+ } catch {
46
+ continue
47
+ }
48
+ const kept = []
49
+ for (const line of content.split("\n")) {
50
+ const p = line.trim()
51
+ if (!p) continue
52
+ if (isOpenmonetaProject(p)) kept.push(p)
53
+ else pruned++
54
+ }
55
+ fs.writeFileSync(reg, kept.length ? kept.join("\n") + "\n" : "")
56
+ }
57
+ return pruned
58
+ }
59
+
60
+ function syncProject(initScript, dir) {
61
+ if (isWindows()) {
62
+ execSync(`bash "${initScript}" "${dir}"`, { stdio: "inherit", shell: true })
63
+ } else {
64
+ execSync(`bash "${initScript}" "${dir}"`, { stdio: "inherit" })
65
+ }
66
+ }
67
+
6
68
  async function run(args) {
7
69
  const checkOnly = args.includes("--check")
8
70
  const autoYes = args.includes("--yes") || args.includes("-y")
9
71
  const skipProjects = args.includes("--skip-projects")
72
+ const allProjects = args.includes("--all-projects")
10
73
  const force = args.includes("--force")
11
74
 
12
75
  const local = getLocalVersion()
@@ -76,24 +139,56 @@ async function run(args) {
76
139
  if (!skipProjects) {
77
140
  const initScript = path.join(pkgRoot, "scripts", "init-project.sh")
78
141
  const cwd = process.cwd()
79
- const docsIndex = path.join(cwd, "docs", "INDEX.md")
80
142
 
81
- try {
82
- require("node:fs").accessSync(docsIndex)
83
- console.log(`\n Syncing project: ${path.basename(cwd)}`)
84
- if (isWindows()) {
85
- execSync(`bash "${initScript}" "${cwd}"`, { stdio: "inherit", shell: true })
86
- } else {
87
- execSync(`bash "${initScript}" "${cwd}"`, { stdio: "inherit" })
143
+ if (allProjects) {
144
+ const pruned = pruneRegistry()
145
+ if (pruned > 0) console.log(`\n Registry: prune ${pruned} entry chết.`)
146
+
147
+ const projects = readRegistry()
148
+ if (isOpenmonetaProject(cwd) && !projects.includes(fs.realpathSync(cwd))) {
149
+ projects.push(fs.realpathSync(cwd))
88
150
  }
89
- console.log(`\n ✅ Project synced.`)
90
- } catch (err) {
91
- if (isWindows()) {
92
- console.log(`\n ⚠ Không thể sync project (cần Git Bash). Cài tại: https://git-scm.com/download/win`)
93
- console.log(` Sau đó chạy thủ công: bash "${initScript}" "${cwd}"`)
151
+
152
+ if (projects.length === 0) {
153
+ console.log(`\n ℹ Registry trống. Init project trước: openmoneta init`)
94
154
  } else {
95
- console.log(`\n Không phát hiện project OpenMoneta ở thư mục hiện tại.`)
96
- console.log(` Sync thủ công: openmoneta init`)
155
+ console.log(`\n Syncing ${projects.length} project (registry)...`)
156
+ let ok = 0
157
+ let fail = 0
158
+ for (const dir of projects) {
159
+ try {
160
+ console.log(`\n ▶ ${path.basename(dir)} (${dir})`)
161
+ syncProject(initScript, dir)
162
+ ok++
163
+ } catch {
164
+ console.error(` ⚠ Sync fail: ${dir}`)
165
+ fail++
166
+ }
167
+ }
168
+ console.log(`\n ✅ Projects synced: ${ok}${fail ? `, failed: ${fail}` : ""}`)
169
+ }
170
+ } else {
171
+ try {
172
+ fs.accessSync(path.join(cwd, "docs", "INDEX.md"))
173
+ console.log(`\n ▶ Syncing project: ${path.basename(cwd)}`)
174
+ syncProject(initScript, cwd)
175
+ console.log(`\n ✅ Project synced.`)
176
+ } catch (err) {
177
+ if (isWindows()) {
178
+ console.log(`\n ⚠ Không thể sync project (cần Git Bash). Cài tại: https://git-scm.com/download/win`)
179
+ console.log(` Sau đó chạy thủ công: bash "${initScript}" "${cwd}"`)
180
+ } else {
181
+ console.log(`\n ℹ Không phát hiện project OpenMoneta ở thư mục hiện tại.`)
182
+ console.log(` Sync thủ công: openmoneta init`)
183
+ }
184
+ }
185
+
186
+ const others = readRegistry().filter(
187
+ (p) => isOpenmonetaProject(p) && !(isOpenmonetaProject(cwd) && p === fs.realpathSync(cwd))
188
+ )
189
+ if (others.length > 0) {
190
+ console.log(`\n ℹ Có ${others.length} project khác trong registry chưa sync. Sync tất cả:`)
191
+ console.log(` openmoneta update --all-projects`)
97
192
  }
98
193
  }
99
194
  }
@@ -101,4 +196,4 @@ async function run(args) {
101
196
  console.log(`\n 🎉 Hoàn tất. Restart Cursor/OpenCode để áp dụng.`)
102
197
  }
103
198
 
104
- module.exports = { run }
199
+ module.exports = { run, _internal: { isOpenmonetaProject, readRegistry, pruneRegistry } }
package/src/lib/paths.js CHANGED
@@ -19,6 +19,11 @@ function versionFilePath(target) {
19
19
  return path.join(CURSOR_DIR, ".openmoneta-version")
20
20
  }
21
21
 
22
+ function getProjectRegistry(target) {
23
+ if (target === "opencode") return path.join(OPENCODE_DIR, ".openmoneta-projects")
24
+ return path.join(CURSOR_DIR, ".openmoneta-projects")
25
+ }
26
+
22
27
  function installedVersion(target) {
23
28
  try {
24
29
  return fs.readFileSync(versionFilePath(target), "utf8").trim()
@@ -52,6 +57,7 @@ module.exports = {
52
57
  OPENCODE_DIR,
53
58
  REPO_DIR,
54
59
  versionFilePath,
60
+ getProjectRegistry,
55
61
  installedVersion,
56
62
  isInstalled,
57
63
  isWindows,
@@ -87,7 +87,7 @@
87
87
 
88
88
  **Core conditional**: `safe-push` (Bước 6 — chỉ khi user yêu cầu push).
89
89
 
90
- **On-demand (chỉ trigger khi user yêu cầu rõ)**: `security-checklist`, `test-strategy`, `automated-testing`, `auth-bypass-testing`, `ui-test-loop`.
90
+ **On-demand (chỉ trigger khi user yêu cầu rõ)**: `security-checklist`, `test-strategy`, `automated-testing`, `auth-bypass-testing`, `ui-test-loop`, `docs-maintenance`.
91
91
 
92
92
  ## Hooks enforce
93
93
 
package/update.ps1 CHANGED
@@ -6,6 +6,7 @@
6
6
  # .\update.ps1 -Yes -Force # global + auto-sync, luôn cài lại
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
+ # .\update.ps1 -AllProjects # global + sync MỌI project trong registry
9
10
  # .\update.ps1 -SkipProjects # global only (old behavior)
10
11
 
11
12
  param(
@@ -13,6 +14,7 @@ param(
13
14
  [switch]$Force,
14
15
  [switch]$Check,
15
16
  [switch]$SkipProjects,
17
+ [switch]$AllProjects,
16
18
  [string[]]$Project
17
19
  )
18
20
 
@@ -83,6 +85,45 @@ function Sync-Project {
83
85
  return $true
84
86
  }
85
87
 
88
+ # Helper: registry files (gắn với install home cố định, không dùng OPENMONETA_HOME)
89
+ function Get-RegistryFiles {
90
+ $cursorReg = Join-Path $env:USERPROFILE ".cursor\.openmoneta-projects"
91
+ $openCodeBase = if ($env:XDG_CONFIG_HOME) { "$env:XDG_CONFIG_HOME\opencode" } else { "$env:USERPROFILE\.config\opencode" }
92
+ $openCodeReg = Join-Path $openCodeBase ".openmoneta-projects"
93
+ return @($cursorReg, $openCodeReg)
94
+ }
95
+
96
+ # Helper: đọc registry (merge 2 home, dedup)
97
+ function Read-Registry {
98
+ $seen = [System.Collections.Generic.HashSet[string]]::new()
99
+ foreach ($f in Get-RegistryFiles) {
100
+ if (-not (Test-Path $f)) { continue }
101
+ foreach ($line in (Get-Content $f)) {
102
+ $p = $line.Trim()
103
+ if (-not $p) { continue }
104
+ $abs = (Resolve-Path $p -ErrorAction SilentlyContinue).Path
105
+ if ($abs) { [void]$seen.Add($abs) }
106
+ }
107
+ }
108
+ return @($seen)
109
+ }
110
+
111
+ # Helper: prune entry chết khỏi registry
112
+ function Invoke-PruneRegistry {
113
+ $pruned = 0
114
+ foreach ($f in Get-RegistryFiles) {
115
+ if (-not (Test-Path $f)) { continue }
116
+ $kept = @()
117
+ foreach ($line in (Get-Content $f)) {
118
+ $p = $line.Trim()
119
+ if (-not $p) { continue }
120
+ if (Test-OpenMonetaProject $p) { $kept += $p } else { $pruned++ }
121
+ }
122
+ Set-Content -Path $f -Value $kept
123
+ }
124
+ return $pruned
125
+ }
126
+
86
127
  # Helper: collect projects to sync
87
128
  function Get-OpenMonetaProjects {
88
129
  $found = @()
@@ -106,9 +147,34 @@ function Get-OpenMonetaProjects {
106
147
  }
107
148
  }
108
149
 
150
+ # Add registry projects khi -AllProjects
151
+ if ($AllProjects) {
152
+ foreach ($rp in (Read-Registry)) {
153
+ if (-not (Test-OpenMonetaProject $rp)) { continue }
154
+ if ($found -notcontains $rp) { $found += $rp }
155
+ }
156
+ }
157
+
109
158
  return $found
110
159
  }
111
160
 
161
+ # Helper: nhắc user còn project trong registry chưa sync (chỉ khi không -AllProjects)
162
+ function Write-RegistryHint {
163
+ param([string[]]$Synced)
164
+ if ($AllProjects) { return }
165
+ $count = 0
166
+ foreach ($rp in (Read-Registry)) {
167
+ if (-not (Test-OpenMonetaProject $rp)) { continue }
168
+ if ($Synced -contains $rp) { continue }
169
+ $count++
170
+ }
171
+ if ($count -gt 0) {
172
+ Write-Host ""
173
+ Write-Host "ℹ Có $count project khác trong registry chưa sync. Sync tất cả:"
174
+ Write-Host " .\update.ps1 -AllProjects"
175
+ }
176
+ }
177
+
112
178
  # === Step 1: Git pull ===
113
179
  $Status = git status --porcelain 2>$null
114
180
  if ($Status) {
@@ -196,6 +262,13 @@ if ($SkipProjects) {
196
262
  exit 0
197
263
  }
198
264
 
265
+ if ($AllProjects) {
266
+ Write-Host ""
267
+ Write-Host "==> -AllProjects: dọn registry + gom mọi project đã đăng ký..."
268
+ $pruned = Invoke-PruneRegistry
269
+ if ($pruned -gt 0) { Write-Host " (registry: prune $pruned entry chết)" }
270
+ }
271
+
199
272
  $Projects = Get-OpenMonetaProjects
200
273
 
201
274
  if ($Projects.Count -eq 0) {
@@ -209,6 +282,7 @@ if ($Projects.Count -eq 0) {
209
282
  Write-Host "Hoặc sync thủ công từng project:"
210
283
  Write-Host " bash $RepoDir\scripts\init-project.sh <path>"
211
284
  }
285
+ Write-RegistryHint -Synced $Projects
212
286
  exit 0
213
287
  }
214
288
 
@@ -250,5 +324,6 @@ if ($SyncFail -gt 0) {
250
324
  Write-Host " Failed: $SyncFail"
251
325
  }
252
326
  Write-Host "================================================"
327
+ Write-RegistryHint -Synced $Projects
253
328
  Write-Host ""
254
329
  Write-Host "Restart Cursor để áp dụng changes."
package/update.sh CHANGED
@@ -8,6 +8,7 @@
8
8
  # bash update.sh --yes --force # global + auto-sync, luôn cài lại
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
+ # bash update.sh --all-projects # global + sync MỌI project trong registry
11
12
  # bash update.sh --skip-projects # global only (old behavior)
12
13
 
13
14
  set -euo pipefail
@@ -20,6 +21,7 @@ AUTO_YES=0
20
21
  SKIP_PROJECTS=0
21
22
  FORCE=0
22
23
  CHECK_ONLY=0
24
+ ALL_PROJECTS=0
23
25
  PROJECT_PATHS=()
24
26
 
25
27
  for arg in "$@"; do
@@ -27,6 +29,7 @@ for arg in "$@"; do
27
29
  --yes|-y) AUTO_YES=1 ;;
28
30
  --force) FORCE=1 ;;
29
31
  --check) CHECK_ONLY=1 ;;
32
+ --all-projects) ALL_PROJECTS=1 ;;
30
33
  --skip-projects) SKIP_PROJECTS=1 ;;
31
34
  --help|-h)
32
35
  grep '^#' "$0" | sed 's/^# \?//' | head -17
@@ -108,6 +111,41 @@ sync_project() {
108
111
  fi
109
112
  }
110
113
 
114
+ # === Helper: registry files (gắn với install home cố định, không dùng OPENMONETA_HOME) ===
115
+ registry_files() {
116
+ printf '%s\n' \
117
+ "$HOME/.cursor/.openmoneta-projects" \
118
+ "${XDG_CONFIG_HOME:-$HOME/.config}/opencode/.openmoneta-projects"
119
+ }
120
+
121
+ # === Helper: đọc registry (merge 2 home, bỏ dòng trống, dedup) ===
122
+ read_registry() {
123
+ local f
124
+ while IFS= read -r f; do
125
+ [[ -f "$f" ]] && cat "$f"
126
+ done < <(registry_files) | awk 'NF' | sort -u
127
+ }
128
+
129
+ # === Helper: prune entry chết khỏi registry (path mất / không còn OpenMoneta project) ===
130
+ prune_registry() {
131
+ local f tmp pruned=0 line
132
+ while IFS= read -r f; do
133
+ [[ -f "$f" ]] || continue
134
+ tmp=$(mktemp)
135
+ while IFS= read -r line; do
136
+ [[ -z "$line" ]] && continue
137
+ if is_openmoneta_project "$line"; then
138
+ echo "$line" >> "$tmp"
139
+ else
140
+ pruned=$((pruned + 1))
141
+ fi
142
+ done < "$f"
143
+ mv "$tmp" "$f"
144
+ done < <(registry_files)
145
+ [[ $pruned -gt 0 ]] && echo " (registry: prune $pruned entry chết)"
146
+ return 0
147
+ }
148
+
111
149
  # === Helper: collect projects to sync ===
112
150
  collect_projects() {
113
151
  local found=()
@@ -116,7 +154,7 @@ collect_projects() {
116
154
  local abs_kit
117
155
  abs_kit="$(cd "$REPO_DIR" && pwd -P)"
118
156
  if is_openmoneta_project "$ORIGINAL_PWD" && [[ "$(cd "$ORIGINAL_PWD" && pwd -P)" != "$abs_kit" ]]; then
119
- found+=("$ORIGINAL_PWD")
157
+ found+=("$(cd "$ORIGINAL_PWD" && pwd -P)")
120
158
  fi
121
159
  fi
122
160
 
@@ -132,6 +170,20 @@ collect_projects() {
132
170
  fi
133
171
  done
134
172
 
173
+ if [[ $ALL_PROJECTS -eq 1 ]]; then
174
+ local rp abs_rp
175
+ while IFS= read -r rp; do
176
+ [[ -z "$rp" ]] && continue
177
+ is_openmoneta_project "$rp" || continue
178
+ abs_rp="$(cd "$rp" && pwd -P)"
179
+ local already=0
180
+ for f in "${found[@]}"; do
181
+ [[ "$f" == "$abs_rp" ]] && already=1 && break
182
+ done
183
+ [[ $already -eq 0 ]] && found+=("$abs_rp")
184
+ done < <(read_registry)
185
+ fi
186
+
135
187
  printf '%s\n' "${found[@]}"
136
188
  }
137
189
 
@@ -220,6 +272,33 @@ if [[ $SKIP_PROJECTS -eq 1 ]]; then
220
272
  exit 0
221
273
  fi
222
274
 
275
+ # === Helper: nhắc user còn project trong registry chưa sync (chỉ khi không --all-projects) ===
276
+ print_registry_hint() {
277
+ [[ $ALL_PROJECTS -eq 1 ]] && return 0
278
+ local count=0 rp abs_rp
279
+ while IFS= read -r rp; do
280
+ [[ -z "$rp" ]] && continue
281
+ is_openmoneta_project "$rp" || continue
282
+ abs_rp="$(cd "$rp" && pwd -P)"
283
+ if [[ -n "$PROJECTS" ]] && printf '%s\n' "$PROJECTS" | grep -qFx "$abs_rp"; then
284
+ continue
285
+ fi
286
+ count=$((count + 1))
287
+ done < <(read_registry)
288
+ if [[ $count -gt 0 ]]; then
289
+ echo ""
290
+ echo "ℹ Có $count project khác trong registry chưa sync. Sync tất cả:"
291
+ echo " bash $REPO_DIR/update.sh --all-projects"
292
+ fi
293
+ return 0
294
+ }
295
+
296
+ if [[ $ALL_PROJECTS -eq 1 ]]; then
297
+ echo ""
298
+ echo "==> --all-projects: dọn registry + gom mọi project đã đăng ký..."
299
+ prune_registry
300
+ fi
301
+
223
302
  PROJECTS=$(collect_projects)
224
303
 
225
304
  if [[ -z "$PROJECTS" ]]; then
@@ -233,6 +312,7 @@ if [[ -z "$PROJECTS" ]]; then
233
312
  echo "Hoặc sync thủ công từng project:"
234
313
  echo " bash $REPO_DIR/scripts/init-project.sh /path/to/project"
235
314
  fi
315
+ print_registry_hint
236
316
  exit 0
237
317
  fi
238
318
 
@@ -274,5 +354,6 @@ if [[ $SYNC_FAIL -gt 0 ]]; then
274
354
  echo " Failed: $SYNC_FAIL"
275
355
  fi
276
356
  echo "================================================"
357
+ print_registry_hint
277
358
  echo ""
278
359
  echo "Restart Cursor để áp dụng changes."