openmoneta-dev-kit 1.11.1 → 1.13.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 +19 -1
- package/opencode/AGENTS.md.tpl +1 -0
- package/opencode/plugins/openmoneta-guard.ts +31 -1
- 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 +225 -17
- package/src/lib/paths.js +6 -0
- package/templates/AGENTS.md.tpl +1 -1
- package/update.ps1 +128 -0
- package/update.sh +147 -9
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.13.0
|
|
@@ -10,12 +10,30 @@ set -euo pipefail
|
|
|
10
10
|
|
|
11
11
|
INPUT_JSON=$(cat 2>/dev/null || echo '{}')
|
|
12
12
|
|
|
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
|
+
|
|
13
30
|
# Reset docs-index marker mỗi session (force re-read INDEX.md)
|
|
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
|
|
18
35
|
fi
|
|
36
|
+
register_project_cursor "$WORKSPACE_FROM_INPUT" 2>/dev/null || true
|
|
19
37
|
fi
|
|
20
38
|
|
|
21
39
|
# === Summary 6 bước (~45 dòng, ~650 tokens) ===
|
|
@@ -44,7 +62,7 @@ SUMMARY='# OpenMoneta Dev Kit v1.7.0 — Quy trình 6 bước (Adaptive Planning
|
|
|
44
62
|
- `security-auditor`, `qa-autonomous`, `ui-tester` — **ON-DEMAND ONLY** (chỉ khi user yêu cầu explicit).
|
|
45
63
|
|
|
46
64
|
## 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").
|
|
65
|
+
`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
66
|
|
|
49
67
|
## Skill core conditional
|
|
50
68
|
`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
|
|
|
@@ -400,6 +400,33 @@ function clearVerifyLoop(root: string) {
|
|
|
400
400
|
fs.rmSync(path.join(root, ".cursor", ".openmoneta-verify-loop.json"), { force: true })
|
|
401
401
|
}
|
|
402
402
|
|
|
403
|
+
// Auto-register project vào OpenCode registry (seed cho `update --all-projects`).
|
|
404
|
+
// Im lặng + try/catch — seed không được phép làm fail guard. Chỉ append khi:
|
|
405
|
+
// project có docs/INDEX.md, OpenCode install home đã cài, path chưa có trong registry.
|
|
406
|
+
function registerProjectOpenCode(root: string) {
|
|
407
|
+
try {
|
|
408
|
+
if (!fs.existsSync(path.join(root, "docs", "INDEX.md"))) return
|
|
409
|
+
const home = process.env.HOME || process.env.USERPROFILE || ""
|
|
410
|
+
const xdg = process.env.XDG_CONFIG_HOME || path.join(home, ".config")
|
|
411
|
+
const ocHome = path.join(xdg, "opencode")
|
|
412
|
+
if (!fs.existsSync(path.join(ocHome, ".openmoneta-version"))) return
|
|
413
|
+
const reg = path.join(ocHome, ".openmoneta-projects")
|
|
414
|
+
const abs = fs.realpathSync(root)
|
|
415
|
+
let lines: string[] = []
|
|
416
|
+
if (fs.existsSync(reg)) {
|
|
417
|
+
lines = fs
|
|
418
|
+
.readFileSync(reg, "utf8")
|
|
419
|
+
.split("\n")
|
|
420
|
+
.map((l) => l.trim())
|
|
421
|
+
.filter(Boolean)
|
|
422
|
+
}
|
|
423
|
+
if (lines.includes(abs)) return
|
|
424
|
+
fs.appendFileSync(reg, `${abs}\n`)
|
|
425
|
+
} catch {
|
|
426
|
+
// im lặng
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
403
430
|
type SessionPromptClient = {
|
|
404
431
|
session?: {
|
|
405
432
|
prompt?: (input: {
|
|
@@ -434,7 +461,7 @@ export const OpenMonetaGuard = async (ctx: GuardContext) => {
|
|
|
434
461
|
{
|
|
435
462
|
loaded_at: new Date().toISOString(),
|
|
436
463
|
root,
|
|
437
|
-
version: "1.
|
|
464
|
+
version: "1.13.0",
|
|
438
465
|
load_count: globalState[globalKey],
|
|
439
466
|
},
|
|
440
467
|
null,
|
|
@@ -442,6 +469,9 @@ export const OpenMonetaGuard = async (ctx: GuardContext) => {
|
|
|
442
469
|
)}\n`,
|
|
443
470
|
)
|
|
444
471
|
|
|
472
|
+
// Seed project vào OpenCode registry để `update --all-projects` thấy project này.
|
|
473
|
+
registerProjectOpenCode(root)
|
|
474
|
+
|
|
445
475
|
// OpenCode does not have Cursor's sessionStart hook, so reset the docs-first
|
|
446
476
|
// marker when the plugin is loaded for a new OpenCode process/session.
|
|
447
477
|
if (fs.existsSync(marker)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openmoneta-dev-kit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.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 + soi ghost (deterministic)
|
|
46
|
+
|
|
47
|
+
```bash
|
|
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 kê GHOST (doc/INDEX trỏ source/module đã mất)
|
|
50
|
+
```
|
|
51
|
+
|
|
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
|
+
|
|
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** (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
|
+
|
|
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,180 @@
|
|
|
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
|
+
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
|
+
|
|
158
|
+
function syncProject(initScript, dir) {
|
|
159
|
+
if (isWindows()) {
|
|
160
|
+
execSync(`bash "${initScript}" "${dir}"`, { stdio: "inherit", shell: true })
|
|
161
|
+
} else {
|
|
162
|
+
execSync(`bash "${initScript}" "${dir}"`, { stdio: "inherit" })
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
6
166
|
async function run(args) {
|
|
7
167
|
const checkOnly = args.includes("--check")
|
|
8
168
|
const autoYes = args.includes("--yes") || args.includes("-y")
|
|
9
169
|
const skipProjects = args.includes("--skip-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
|
|
10
178
|
const force = args.includes("--force")
|
|
11
179
|
|
|
12
180
|
const local = getLocalVersion()
|
|
@@ -76,24 +244,61 @@ async function run(args) {
|
|
|
76
244
|
if (!skipProjects) {
|
|
77
245
|
const initScript = path.join(pkgRoot, "scripts", "init-project.sh")
|
|
78
246
|
const cwd = process.cwd()
|
|
79
|
-
const docsIndex = path.join(cwd, "docs", "INDEX.md")
|
|
80
247
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
248
|
+
if (scan) {
|
|
249
|
+
console.log(`\n ▶ --scan: dò OM project để seed registry...`)
|
|
250
|
+
scanSeed(scanRoot, pkgRoot)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (allProjects) {
|
|
254
|
+
const pruned = pruneRegistry()
|
|
255
|
+
if (pruned > 0) console.log(`\n ℹ Registry: prune ${pruned} entry chết.`)
|
|
256
|
+
|
|
257
|
+
const projects = readRegistry()
|
|
258
|
+
if (isOpenmonetaProject(cwd) && !projects.includes(fs.realpathSync(cwd))) {
|
|
259
|
+
projects.push(fs.realpathSync(cwd))
|
|
88
260
|
}
|
|
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}"`)
|
|
261
|
+
|
|
262
|
+
if (projects.length === 0) {
|
|
263
|
+
console.log(`\n ℹ Registry trống. Init project trước: openmoneta init`)
|
|
94
264
|
} else {
|
|
95
|
-
console.log(`\n
|
|
96
|
-
|
|
265
|
+
console.log(`\n ▶ Syncing ${projects.length} project (registry)...`)
|
|
266
|
+
let ok = 0
|
|
267
|
+
let fail = 0
|
|
268
|
+
for (const dir of projects) {
|
|
269
|
+
try {
|
|
270
|
+
console.log(`\n ▶ ${path.basename(dir)} (${dir})`)
|
|
271
|
+
syncProject(initScript, dir)
|
|
272
|
+
ok++
|
|
273
|
+
} catch {
|
|
274
|
+
console.error(` ⚠ Sync fail: ${dir}`)
|
|
275
|
+
fail++
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
console.log(`\n ✅ Projects synced: ${ok}${fail ? `, failed: ${fail}` : ""}`)
|
|
279
|
+
}
|
|
280
|
+
} else {
|
|
281
|
+
try {
|
|
282
|
+
fs.accessSync(path.join(cwd, "docs", "INDEX.md"))
|
|
283
|
+
console.log(`\n ▶ Syncing project: ${path.basename(cwd)}`)
|
|
284
|
+
syncProject(initScript, cwd)
|
|
285
|
+
console.log(`\n ✅ Project synced.`)
|
|
286
|
+
} catch (err) {
|
|
287
|
+
if (isWindows()) {
|
|
288
|
+
console.log(`\n ⚠ Không thể sync project (cần Git Bash). Cài tại: https://git-scm.com/download/win`)
|
|
289
|
+
console.log(` Sau đó chạy thủ công: bash "${initScript}" "${cwd}"`)
|
|
290
|
+
} else {
|
|
291
|
+
console.log(`\n ℹ Không phát hiện project OpenMoneta ở thư mục hiện tại.`)
|
|
292
|
+
console.log(` Sync thủ công: openmoneta init`)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const others = readRegistry().filter(
|
|
297
|
+
(p) => isOpenmonetaProject(p) && !(isOpenmonetaProject(cwd) && p === fs.realpathSync(cwd))
|
|
298
|
+
)
|
|
299
|
+
if (others.length > 0) {
|
|
300
|
+
console.log(`\n ℹ Có ${others.length} project khác trong registry chưa sync. Sync tất cả:`)
|
|
301
|
+
console.log(` openmoneta update --all-projects`)
|
|
97
302
|
}
|
|
98
303
|
}
|
|
99
304
|
}
|
|
@@ -101,4 +306,7 @@ async function run(args) {
|
|
|
101
306
|
console.log(`\n 🎉 Hoàn tất. Restart Cursor/OpenCode để áp dụng.`)
|
|
102
307
|
}
|
|
103
308
|
|
|
104
|
-
module.exports = {
|
|
309
|
+
module.exports = {
|
|
310
|
+
run,
|
|
311
|
+
_internal: { isOpenmonetaProject, readRegistry, pruneRegistry, findProjects, scanSeed },
|
|
312
|
+
}
|
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,8 @@
|
|
|
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
|
|
10
|
+
# .\update.ps1 -Scan [-ScanRoot path] # dò OM project (mặc định $HOME) → seed registry → sync all
|
|
9
11
|
# .\update.ps1 -SkipProjects # global only (old behavior)
|
|
10
12
|
|
|
11
13
|
param(
|
|
@@ -13,6 +15,9 @@ param(
|
|
|
13
15
|
[switch]$Force,
|
|
14
16
|
[switch]$Check,
|
|
15
17
|
[switch]$SkipProjects,
|
|
18
|
+
[switch]$AllProjects,
|
|
19
|
+
[switch]$Scan,
|
|
20
|
+
[string]$ScanRoot,
|
|
16
21
|
[string[]]$Project
|
|
17
22
|
)
|
|
18
23
|
|
|
@@ -83,6 +88,88 @@ function Sync-Project {
|
|
|
83
88
|
return $true
|
|
84
89
|
}
|
|
85
90
|
|
|
91
|
+
# Helper: registry files (gắn với install home cố định, không dùng OPENMONETA_HOME)
|
|
92
|
+
function Get-RegistryFiles {
|
|
93
|
+
$cursorReg = Join-Path $env:USERPROFILE ".cursor\.openmoneta-projects"
|
|
94
|
+
$openCodeBase = if ($env:XDG_CONFIG_HOME) { "$env:XDG_CONFIG_HOME\opencode" } else { "$env:USERPROFILE\.config\opencode" }
|
|
95
|
+
$openCodeReg = Join-Path $openCodeBase ".openmoneta-projects"
|
|
96
|
+
return @($cursorReg, $openCodeReg)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# Helper: đọc registry (merge 2 home, dedup)
|
|
100
|
+
function Read-Registry {
|
|
101
|
+
$seen = [System.Collections.Generic.HashSet[string]]::new()
|
|
102
|
+
foreach ($f in Get-RegistryFiles) {
|
|
103
|
+
if (-not (Test-Path $f)) { continue }
|
|
104
|
+
foreach ($line in (Get-Content $f)) {
|
|
105
|
+
$p = $line.Trim()
|
|
106
|
+
if (-not $p) { continue }
|
|
107
|
+
$abs = (Resolve-Path $p -ErrorAction SilentlyContinue).Path
|
|
108
|
+
if ($abs) { [void]$seen.Add($abs) }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return @($seen)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# Helper: prune entry chết khỏi registry
|
|
115
|
+
function Invoke-PruneRegistry {
|
|
116
|
+
$pruned = 0
|
|
117
|
+
foreach ($f in Get-RegistryFiles) {
|
|
118
|
+
if (-not (Test-Path $f)) { continue }
|
|
119
|
+
$kept = @()
|
|
120
|
+
foreach ($line in (Get-Content $f)) {
|
|
121
|
+
$p = $line.Trim()
|
|
122
|
+
if (-not $p) { continue }
|
|
123
|
+
if (Test-OpenMonetaProject $p) { $kept += $p } else { $pruned++ }
|
|
124
|
+
}
|
|
125
|
+
Set-Content -Path $f -Value $kept
|
|
126
|
+
}
|
|
127
|
+
return $pruned
|
|
128
|
+
}
|
|
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
|
+
|
|
86
173
|
# Helper: collect projects to sync
|
|
87
174
|
function Get-OpenMonetaProjects {
|
|
88
175
|
$found = @()
|
|
@@ -106,9 +193,34 @@ function Get-OpenMonetaProjects {
|
|
|
106
193
|
}
|
|
107
194
|
}
|
|
108
195
|
|
|
196
|
+
# Add registry projects khi -AllProjects
|
|
197
|
+
if ($AllProjects) {
|
|
198
|
+
foreach ($rp in (Read-Registry)) {
|
|
199
|
+
if (-not (Test-OpenMonetaProject $rp)) { continue }
|
|
200
|
+
if ($found -notcontains $rp) { $found += $rp }
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
109
204
|
return $found
|
|
110
205
|
}
|
|
111
206
|
|
|
207
|
+
# Helper: nhắc user còn project trong registry chưa sync (chỉ khi không -AllProjects)
|
|
208
|
+
function Write-RegistryHint {
|
|
209
|
+
param([string[]]$Synced)
|
|
210
|
+
if ($AllProjects) { return }
|
|
211
|
+
$count = 0
|
|
212
|
+
foreach ($rp in (Read-Registry)) {
|
|
213
|
+
if (-not (Test-OpenMonetaProject $rp)) { continue }
|
|
214
|
+
if ($Synced -contains $rp) { continue }
|
|
215
|
+
$count++
|
|
216
|
+
}
|
|
217
|
+
if ($count -gt 0) {
|
|
218
|
+
Write-Host ""
|
|
219
|
+
Write-Host "ℹ Có $count project khác trong registry chưa sync. Sync tất cả:"
|
|
220
|
+
Write-Host " .\update.ps1 -AllProjects"
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
112
224
|
# === Step 1: Git pull ===
|
|
113
225
|
$Status = git status --porcelain 2>$null
|
|
114
226
|
if ($Status) {
|
|
@@ -196,6 +308,20 @@ if ($SkipProjects) {
|
|
|
196
308
|
exit 0
|
|
197
309
|
}
|
|
198
310
|
|
|
311
|
+
if ($Scan) {
|
|
312
|
+
$AllProjects = $true
|
|
313
|
+
Write-Host ""
|
|
314
|
+
Write-Host "==> -Scan: dò OM project để seed registry..."
|
|
315
|
+
Invoke-ScanSeed
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if ($AllProjects) {
|
|
319
|
+
Write-Host ""
|
|
320
|
+
Write-Host "==> -AllProjects: dọn registry + gom mọi project đã đăng ký..."
|
|
321
|
+
$pruned = Invoke-PruneRegistry
|
|
322
|
+
if ($pruned -gt 0) { Write-Host " (registry: prune $pruned entry chết)" }
|
|
323
|
+
}
|
|
324
|
+
|
|
199
325
|
$Projects = Get-OpenMonetaProjects
|
|
200
326
|
|
|
201
327
|
if ($Projects.Count -eq 0) {
|
|
@@ -209,6 +335,7 @@ if ($Projects.Count -eq 0) {
|
|
|
209
335
|
Write-Host "Hoặc sync thủ công từng project:"
|
|
210
336
|
Write-Host " bash $RepoDir\scripts\init-project.sh <path>"
|
|
211
337
|
}
|
|
338
|
+
Write-RegistryHint -Synced $Projects
|
|
212
339
|
exit 0
|
|
213
340
|
}
|
|
214
341
|
|
|
@@ -250,5 +377,6 @@ if ($SyncFail -gt 0) {
|
|
|
250
377
|
Write-Host " Failed: $SyncFail"
|
|
251
378
|
}
|
|
252
379
|
Write-Host "================================================"
|
|
380
|
+
Write-RegistryHint -Synced $Projects
|
|
253
381
|
Write-Host ""
|
|
254
382
|
Write-Host "Restart Cursor để áp dụng changes."
|
package/update.sh
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
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
|
|
12
|
+
# bash update.sh --scan [root] # dò OM project (mặc định $HOME) → seed registry → sync all
|
|
11
13
|
# bash update.sh --skip-projects # global only (old behavior)
|
|
12
14
|
|
|
13
15
|
set -euo pipefail
|
|
@@ -20,30 +22,43 @@ AUTO_YES=0
|
|
|
20
22
|
SKIP_PROJECTS=0
|
|
21
23
|
FORCE=0
|
|
22
24
|
CHECK_ONLY=0
|
|
25
|
+
ALL_PROJECTS=0
|
|
26
|
+
SCAN=0
|
|
27
|
+
SCAN_ROOT=""
|
|
23
28
|
PROJECT_PATHS=()
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
case "$
|
|
30
|
+
while [[ $# -gt 0 ]]; do
|
|
31
|
+
case "$1" in
|
|
27
32
|
--yes|-y) AUTO_YES=1 ;;
|
|
28
33
|
--force) FORCE=1 ;;
|
|
29
34
|
--check) CHECK_ONLY=1 ;;
|
|
35
|
+
--all-projects) ALL_PROJECTS=1 ;;
|
|
30
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
|
+
;;
|
|
31
45
|
--help|-h)
|
|
32
|
-
grep '^#' "$0" | sed 's/^# \?//' | head -
|
|
46
|
+
grep '^#' "$0" | sed 's/^# \?//' | head -18
|
|
33
47
|
exit 0
|
|
34
48
|
;;
|
|
35
49
|
--project)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
50
|
+
if [[ -n "${2:-}" && "${2:-}" != -* ]]; then
|
|
51
|
+
PROJECT_PATHS+=("$2")
|
|
52
|
+
shift
|
|
39
53
|
fi
|
|
40
54
|
;;
|
|
41
55
|
*)
|
|
42
|
-
if [[ "$
|
|
43
|
-
PROJECT_PATHS+=("$
|
|
56
|
+
if [[ "$1" != -* ]]; then
|
|
57
|
+
PROJECT_PATHS+=("$1")
|
|
44
58
|
fi
|
|
45
59
|
;;
|
|
46
60
|
esac
|
|
61
|
+
shift
|
|
47
62
|
done
|
|
48
63
|
|
|
49
64
|
cd "$REPO_DIR"
|
|
@@ -108,6 +123,80 @@ sync_project() {
|
|
|
108
123
|
fi
|
|
109
124
|
}
|
|
110
125
|
|
|
126
|
+
# === Helper: registry files (gắn với install home cố định, không dùng OPENMONETA_HOME) ===
|
|
127
|
+
registry_files() {
|
|
128
|
+
printf '%s\n' \
|
|
129
|
+
"$HOME/.cursor/.openmoneta-projects" \
|
|
130
|
+
"${XDG_CONFIG_HOME:-$HOME/.config}/opencode/.openmoneta-projects"
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# === Helper: đọc registry (merge 2 home, bỏ dòng trống, dedup) ===
|
|
134
|
+
read_registry() {
|
|
135
|
+
local f
|
|
136
|
+
while IFS= read -r f; do
|
|
137
|
+
[[ -f "$f" ]] && cat "$f"
|
|
138
|
+
done < <(registry_files) | awk 'NF' | sort -u
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# === Helper: prune entry chết khỏi registry (path mất / không còn OpenMoneta project) ===
|
|
142
|
+
prune_registry() {
|
|
143
|
+
local f tmp pruned=0 line
|
|
144
|
+
while IFS= read -r f; do
|
|
145
|
+
[[ -f "$f" ]] || continue
|
|
146
|
+
tmp=$(mktemp)
|
|
147
|
+
while IFS= read -r line; do
|
|
148
|
+
[[ -z "$line" ]] && continue
|
|
149
|
+
if is_openmoneta_project "$line"; then
|
|
150
|
+
echo "$line" >> "$tmp"
|
|
151
|
+
else
|
|
152
|
+
pruned=$((pruned + 1))
|
|
153
|
+
fi
|
|
154
|
+
done < "$f"
|
|
155
|
+
mv "$tmp" "$f"
|
|
156
|
+
done < <(registry_files)
|
|
157
|
+
[[ $pruned -gt 0 ]] && echo " (registry: prune $pruned entry chết)"
|
|
158
|
+
return 0
|
|
159
|
+
}
|
|
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
|
+
|
|
111
200
|
# === Helper: collect projects to sync ===
|
|
112
201
|
collect_projects() {
|
|
113
202
|
local found=()
|
|
@@ -116,7 +205,7 @@ collect_projects() {
|
|
|
116
205
|
local abs_kit
|
|
117
206
|
abs_kit="$(cd "$REPO_DIR" && pwd -P)"
|
|
118
207
|
if is_openmoneta_project "$ORIGINAL_PWD" && [[ "$(cd "$ORIGINAL_PWD" && pwd -P)" != "$abs_kit" ]]; then
|
|
119
|
-
found+=("$ORIGINAL_PWD")
|
|
208
|
+
found+=("$(cd "$ORIGINAL_PWD" && pwd -P)")
|
|
120
209
|
fi
|
|
121
210
|
fi
|
|
122
211
|
|
|
@@ -132,6 +221,20 @@ collect_projects() {
|
|
|
132
221
|
fi
|
|
133
222
|
done
|
|
134
223
|
|
|
224
|
+
if [[ $ALL_PROJECTS -eq 1 ]]; then
|
|
225
|
+
local rp abs_rp
|
|
226
|
+
while IFS= read -r rp; do
|
|
227
|
+
[[ -z "$rp" ]] && continue
|
|
228
|
+
is_openmoneta_project "$rp" || continue
|
|
229
|
+
abs_rp="$(cd "$rp" && pwd -P)"
|
|
230
|
+
local already=0
|
|
231
|
+
for f in "${found[@]}"; do
|
|
232
|
+
[[ "$f" == "$abs_rp" ]] && already=1 && break
|
|
233
|
+
done
|
|
234
|
+
[[ $already -eq 0 ]] && found+=("$abs_rp")
|
|
235
|
+
done < <(read_registry)
|
|
236
|
+
fi
|
|
237
|
+
|
|
135
238
|
printf '%s\n' "${found[@]}"
|
|
136
239
|
}
|
|
137
240
|
|
|
@@ -220,6 +323,39 @@ if [[ $SKIP_PROJECTS -eq 1 ]]; then
|
|
|
220
323
|
exit 0
|
|
221
324
|
fi
|
|
222
325
|
|
|
326
|
+
# === Helper: nhắc user còn project trong registry chưa sync (chỉ khi không --all-projects) ===
|
|
327
|
+
print_registry_hint() {
|
|
328
|
+
[[ $ALL_PROJECTS -eq 1 ]] && return 0
|
|
329
|
+
local count=0 rp abs_rp
|
|
330
|
+
while IFS= read -r rp; do
|
|
331
|
+
[[ -z "$rp" ]] && continue
|
|
332
|
+
is_openmoneta_project "$rp" || continue
|
|
333
|
+
abs_rp="$(cd "$rp" && pwd -P)"
|
|
334
|
+
if [[ -n "$PROJECTS" ]] && printf '%s\n' "$PROJECTS" | grep -qFx "$abs_rp"; then
|
|
335
|
+
continue
|
|
336
|
+
fi
|
|
337
|
+
count=$((count + 1))
|
|
338
|
+
done < <(read_registry)
|
|
339
|
+
if [[ $count -gt 0 ]]; then
|
|
340
|
+
echo ""
|
|
341
|
+
echo "ℹ Có $count project khác trong registry chưa sync. Sync tất cả:"
|
|
342
|
+
echo " bash $REPO_DIR/update.sh --all-projects"
|
|
343
|
+
fi
|
|
344
|
+
return 0
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if [[ $SCAN -eq 1 ]]; then
|
|
348
|
+
echo ""
|
|
349
|
+
echo "==> --scan: dò OM project để seed registry..."
|
|
350
|
+
scan_seed
|
|
351
|
+
fi
|
|
352
|
+
|
|
353
|
+
if [[ $ALL_PROJECTS -eq 1 ]]; then
|
|
354
|
+
echo ""
|
|
355
|
+
echo "==> --all-projects: dọn registry + gom mọi project đã đăng ký..."
|
|
356
|
+
prune_registry
|
|
357
|
+
fi
|
|
358
|
+
|
|
223
359
|
PROJECTS=$(collect_projects)
|
|
224
360
|
|
|
225
361
|
if [[ -z "$PROJECTS" ]]; then
|
|
@@ -233,6 +369,7 @@ if [[ -z "$PROJECTS" ]]; then
|
|
|
233
369
|
echo "Hoặc sync thủ công từng project:"
|
|
234
370
|
echo " bash $REPO_DIR/scripts/init-project.sh /path/to/project"
|
|
235
371
|
fi
|
|
372
|
+
print_registry_hint
|
|
236
373
|
exit 0
|
|
237
374
|
fi
|
|
238
375
|
|
|
@@ -274,5 +411,6 @@ if [[ $SYNC_FAIL -gt 0 ]]; then
|
|
|
274
411
|
echo " Failed: $SYNC_FAIL"
|
|
275
412
|
fi
|
|
276
413
|
echo "================================================"
|
|
414
|
+
print_registry_hint
|
|
277
415
|
echo ""
|
|
278
416
|
echo "Restart Cursor để áp dụng changes."
|