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 +5 -3
- package/VERSION +1 -1
- package/hooks/inject-process-context.sh +1 -1
- package/opencode/AGENTS.md.tpl +1 -0
- package/package.json +1 -1
- package/scripts/docs-audit.sh +185 -0
- package/scripts/init-project.sh +19 -0
- package/skills/docs-maintenance/SKILL.md +106 -0
- package/src/cli.js +5 -0
- package/src/commands/docs.js +37 -0
- package/src/commands/update.js +112 -17
- package/src/lib/paths.js +6 -0
- package/templates/AGENTS.md.tpl +1 -1
- package/update.ps1 +75 -0
- package/update.sh +82 -1
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)
|
|
6
6
|
[](./docs/INDEX.md)
|
|
7
7
|
[]()
|
|
8
8
|
[](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
|
-
- **
|
|
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.
|
|
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.
|
package/opencode/AGENTS.md.tpl
CHANGED
|
@@ -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.
|
|
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
|
package/scripts/init-project.sh
CHANGED
|
@@ -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 }
|
package/src/commands/update.js
CHANGED
|
@@ -1,12 +1,75 @@
|
|
|
1
1
|
const { execSync } = require("node:child_process")
|
|
2
2
|
const path = require("node:path")
|
|
3
|
-
const
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
console.log(`\n
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
|
96
|
-
|
|
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,
|
package/templates/AGENTS.md.tpl
CHANGED
|
@@ -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."
|