openmoneta-dev-kit 1.9.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.
Files changed (46) hide show
  1. package/README.md +103 -0
  2. package/agents/qa-autonomous.md +131 -0
  3. package/agents/requirement-analyst.md +98 -0
  4. package/agents/security-auditor.md +120 -0
  5. package/agents/ui-tester.md +186 -0
  6. package/bin/openmoneta.js +11 -0
  7. package/hooks/check-plan-exists.sh +154 -0
  8. package/hooks/enforce-docs-first.sh +169 -0
  9. package/hooks/inject-process-context.sh +117 -0
  10. package/hooks/track-changes.sh +46 -0
  11. package/hooks/verify-completion.sh +165 -0
  12. package/hooks.json +30 -0
  13. package/opencode/AGENTS.md.tpl +38 -0
  14. package/opencode/agents/qa-autonomous.md +42 -0
  15. package/opencode/agents/requirement-analyst.md +51 -0
  16. package/opencode/agents/security-auditor.md +46 -0
  17. package/opencode/agents/ui-tester.md +43 -0
  18. package/opencode/plugins/openmoneta-guard.ts +389 -0
  19. package/package.json +41 -0
  20. package/scripts/debug-hooks.sh +54 -0
  21. package/scripts/init-project.sh +438 -0
  22. package/scripts/list-affected-modules.sh +74 -0
  23. package/skills/auth-bypass-testing/SKILL.md +236 -0
  24. package/skills/automated-testing/SKILL.md +162 -0
  25. package/skills/automated-testing/scripts/install-playwright.sh +134 -0
  26. package/skills/module-architect/SKILL.md +256 -0
  27. package/skills/plan-writer/SKILL.md +229 -0
  28. package/skills/requirement-analysis/SKILL.md +163 -0
  29. package/skills/safe-push/SKILL.md +182 -0
  30. package/skills/security-checklist/SKILL.md +116 -0
  31. package/skills/test-strategy/SKILL.md +135 -0
  32. package/skills/ui-test-loop/SKILL.md +161 -0
  33. package/src/cli.js +63 -0
  34. package/src/commands/check.js +30 -0
  35. package/src/commands/init.js +43 -0
  36. package/src/commands/install.js +50 -0
  37. package/src/commands/uninstall.js +74 -0
  38. package/src/commands/update.js +81 -0
  39. package/src/lib/paths.js +46 -0
  40. package/src/lib/version.js +45 -0
  41. package/templates/AGENTS.md.tpl +106 -0
  42. package/templates/docs-INDEX.md.tpl +62 -0
  43. package/templates/env.test.tpl +16 -0
  44. package/templates/karpathy-reference.md +49 -0
  45. package/templates/plans-INDEX.md.tpl +38 -0
  46. package/templates/playwright.config.ts.tpl +44 -0
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env bash
2
+ # debug-hooks.sh — kiểm tra nhanh trạng thái hooks OpenMoneta trong 1 project.
3
+ #
4
+ # Usage:
5
+ # bash ~/.cursor/scripts/debug-hooks.sh /path/to/project
6
+ # bash ~/.cursor/scripts/debug-hooks.sh . # default
7
+
8
+ set -euo pipefail
9
+
10
+ PROJECT="${1:-$(pwd)}"
11
+ if [[ -d "$PROJECT" ]]; then
12
+ PROJECT="$(cd "$PROJECT" && pwd -P)"
13
+ fi
14
+
15
+ MARKER="$PROJECT/.cursor/.docs-index-read"
16
+ CHANGES="$PROJECT/.cursor/.session-changes.json"
17
+ INDEX="$PROJECT/docs/INDEX.md"
18
+
19
+ echo "OpenMoneta Hook Debug"
20
+ echo "====================="
21
+ echo "Project: $PROJECT"
22
+ echo "Version: $(cat "$HOME/.cursor/.openmoneta-version" 2>/dev/null || echo "unknown")"
23
+ echo ""
24
+
25
+ echo "Project files:"
26
+ [[ -f "$INDEX" ]] && echo " OK docs/INDEX.md: $INDEX" || echo " MISSING docs/INDEX.md: $INDEX"
27
+ [[ -d "$PROJECT/plans" ]] && echo " OK plans/: $PROJECT/plans" || echo " MISSING plans/: $PROJECT/plans"
28
+ echo ""
29
+
30
+ echo "Session markers:"
31
+ [[ -f "$MARKER" ]] && echo " OK docs marker exists: $MARKER" || echo " MISSING docs marker: $MARKER"
32
+ [[ -f "$CHANGES" ]] && echo " OK session changes: $CHANGES" || echo " MISSING session changes: $CHANGES"
33
+ echo ""
34
+
35
+ echo "Installed hooks:"
36
+ for hook in inject-process-context.sh check-plan-exists.sh enforce-docs-first.sh track-changes.sh verify-completion.sh; do
37
+ path="$HOME/.cursor/hooks/$hook"
38
+ if [[ -x "$path" ]]; then
39
+ echo " OK executable: $path"
40
+ elif [[ -f "$path" ]]; then
41
+ echo " WARN not executable: $path"
42
+ else
43
+ echo " MISSING: $path"
44
+ fi
45
+ done
46
+ echo ""
47
+
48
+ echo "Quick enforce-docs-first check:"
49
+ if [[ -f "$HOME/.cursor/hooks/enforce-docs-first.sh" ]]; then
50
+ printf '{"workspace_roots":["%s"],"tool_name":"Read","tool_input":{"file_path":"%s/src/example.ts"}}\n' "$PROJECT" "$PROJECT" \
51
+ | bash "$HOME/.cursor/hooks/enforce-docs-first.sh"
52
+ else
53
+ echo " Cannot run: enforce-docs-first.sh missing"
54
+ fi
@@ -0,0 +1,438 @@
1
+ #!/usr/bin/env bash
2
+ # init-project.sh — Khởi tạo cấu trúc OpenMoneta Dev Kit cho 1 project (v1.7.x Lean Mode).
3
+ #
4
+ # IDEMPOTENT: chạy nhiều lần sẽ re-sync file managed và giữ các block riêng của project.
5
+ #
6
+ # v1.7.x chỉ tạo 4 file thiết yếu cho AI navigation:
7
+ # - AGENTS.md — quy trình 6 bước
8
+ # - docs/INDEX.md — entry point + Token Routing
9
+ # - plans/INDEX.md — bảng theo dõi plans
10
+ # - .gitignore — patterns chuẩn
11
+ #
12
+ # Các file/folder khác (tests/, .env.test, docs/architecture, CHANGELOG, ...) chỉ tạo khi user yêu cầu cụ thể (on-demand skills).
13
+ #
14
+ # Usage: bash ~/.cursor/scripts/init-project.sh [project-path]
15
+ # (default: pwd)
16
+
17
+ set -euo pipefail
18
+
19
+ PROJECT_DIR="${1:-$(pwd)}"
20
+ PROJECT_DIR="$(cd "$PROJECT_DIR" 2>/dev/null && pwd || echo "$PROJECT_DIR")"
21
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd || pwd)"
22
+ SCRIPT_HOME="$(cd "$SCRIPT_DIR/.." 2>/dev/null && pwd || echo "")"
23
+
24
+ if [[ ! -d "$PROJECT_DIR" ]]; then
25
+ echo "[!] Project dir không tồn tại: $PROJECT_DIR" >&2
26
+ exit 1
27
+ fi
28
+
29
+ OPENMONETA_HOME="${OPENMONETA_HOME:-}"
30
+ if [[ -z "$OPENMONETA_HOME" ]]; then
31
+ if [[ -n "$SCRIPT_HOME" && -d "$SCRIPT_HOME/templates" ]]; then
32
+ OPENMONETA_HOME="$SCRIPT_HOME"
33
+ elif [[ -d "$HOME/.cursor/templates" ]]; then
34
+ OPENMONETA_HOME="$HOME/.cursor"
35
+ elif [[ -d "${XDG_CONFIG_HOME:-$HOME/.config}/opencode/templates" ]]; then
36
+ OPENMONETA_HOME="${XDG_CONFIG_HOME:-$HOME/.config}/opencode"
37
+ else
38
+ OPENMONETA_HOME="$HOME/.cursor"
39
+ fi
40
+ fi
41
+
42
+ TEMPLATES="$OPENMONETA_HOME/templates"
43
+ if [[ ! -d "$TEMPLATES" ]]; then
44
+ echo "[!] Templates dir không tồn tại: $TEMPLATES" >&2
45
+ echo " Cài Cursor: bash install.sh" >&2
46
+ echo " Cài OpenCode: bash install-opencode.sh" >&2
47
+ exit 1
48
+ fi
49
+ KIT_VERSION=$(cat "$OPENMONETA_HOME/.openmoneta-version" 2>/dev/null || echo "1.7.x")
50
+
51
+ echo "==> Init OpenMoneta Dev Kit (Lean Mode v$KIT_VERSION) cho: $PROJECT_DIR"
52
+ echo ""
53
+
54
+ cd "$PROJECT_DIR"
55
+
56
+ CREATED=()
57
+ UPDATED=()
58
+ SKIPPED=()
59
+ BACKUPS=()
60
+ SYNC_BACKUP_DIR=""
61
+
62
+ ensure_dir() {
63
+ local dir="$1"
64
+ if [[ ! -d "$dir" ]]; then
65
+ mkdir -p "$dir"
66
+ CREATED+=("dir: $dir/")
67
+ fi
68
+ }
69
+
70
+ copy_if_missing() {
71
+ local src="$1"
72
+ local dst="$2"
73
+ if [[ -f "$dst" ]]; then
74
+ SKIPPED+=("file: $dst (đã tồn tại)")
75
+ return
76
+ fi
77
+ cp "$src" "$dst"
78
+ CREATED+=("file: $dst")
79
+ }
80
+
81
+ ensure_sync_backup_dir() {
82
+ if [[ -z "$SYNC_BACKUP_DIR" ]]; then
83
+ SYNC_BACKUP_DIR=".cursor/backups/init-sync-$(date +%Y%m%d-%H%M%S)"
84
+ mkdir -p "$SYNC_BACKUP_DIR"
85
+ fi
86
+ }
87
+
88
+ backup_before_sync() {
89
+ local dst="$1"
90
+ ensure_sync_backup_dir
91
+ local safe_name
92
+ safe_name="${dst//\//__}"
93
+ cp -p "$dst" "$SYNC_BACKUP_DIR/$safe_name"
94
+ BACKUPS+=("$dst -> $SYNC_BACKUP_DIR/$safe_name")
95
+ }
96
+
97
+ extract_block() {
98
+ local src="$1"
99
+ local block="$2"
100
+ local out="$3"
101
+ local begin="<!-- BEGIN $block -->"
102
+ local end="<!-- END $block -->"
103
+
104
+ awk -v begin="$begin" -v end="$end" '
105
+ $0 == begin { in_block = 1; found_begin = 1; next }
106
+ $0 == end {
107
+ if (in_block) { found_end = 1 }
108
+ in_block = 0
109
+ next
110
+ }
111
+ in_block { print }
112
+ END { exit(found_begin && found_end ? 0 : 1) }
113
+ ' "$src" > "$out"
114
+ }
115
+
116
+ extract_section_body() {
117
+ local src="$1"
118
+ local heading="$2"
119
+ local out="$3"
120
+
121
+ awk -v heading="$heading" '
122
+ $0 == heading { in_section = 1; found = 1; next }
123
+ in_section && /^##[[:space:]]/ { exit }
124
+ in_section { print }
125
+ END { exit(found ? 0 : 1) }
126
+ ' "$src" > "$out"
127
+ }
128
+
129
+ extract_between_sections() {
130
+ local src="$1"
131
+ local start_heading="$2"
132
+ local stop_heading="$3"
133
+ local out="$4"
134
+
135
+ awk -v start_heading="$start_heading" -v stop_heading="$stop_heading" '
136
+ $0 == start_heading { in_range = 1; found = 1; print; next }
137
+ in_range && $0 == stop_heading { exit }
138
+ in_range { print }
139
+ END { exit(found ? 0 : 1) }
140
+ ' "$src" > "$out"
141
+ }
142
+
143
+ extract_section_table() {
144
+ local src="$1"
145
+ local heading="$2"
146
+ local out="$3"
147
+
148
+ awk -v heading="$heading" '
149
+ $0 == heading { in_section = 1; next }
150
+ in_section && /^##[[:space:]]/ { exit }
151
+ in_section && /^\|/ {
152
+ if (pending_blank) {
153
+ print ""
154
+ pending_blank = 0
155
+ }
156
+ found = 1
157
+ print
158
+ next
159
+ }
160
+ in_section && found && /^[[:space:]]*$/ {
161
+ pending_blank = 1
162
+ next
163
+ }
164
+ END { exit(found ? 0 : 1) }
165
+ ' "$src" > "$out"
166
+ }
167
+
168
+ replace_block() {
169
+ local target="$1"
170
+ local block="$2"
171
+ local content_file="$3"
172
+ local tmp
173
+ tmp=$(mktemp)
174
+
175
+ local begin="<!-- BEGIN $block -->"
176
+ local end="<!-- END $block -->"
177
+
178
+ awk -v begin="$begin" -v end="$end" -v content_file="$content_file" '
179
+ $0 == begin {
180
+ print
181
+ while ((getline line < content_file) > 0) {
182
+ print line
183
+ }
184
+ close(content_file)
185
+ in_block = 1
186
+ next
187
+ }
188
+ $0 == end {
189
+ in_block = 0
190
+ print
191
+ next
192
+ }
193
+ !in_block { print }
194
+ ' "$target" > "$tmp"
195
+
196
+ mv "$tmp" "$target"
197
+ }
198
+
199
+ sync_agents_md() {
200
+ local src="$TEMPLATES/AGENTS.md.tpl"
201
+ local dst="AGENTS.md"
202
+
203
+ if [[ ! -f "$dst" ]]; then
204
+ cp "$src" "$dst"
205
+ CREATED+=("file: $dst")
206
+ return
207
+ fi
208
+
209
+ local rendered override
210
+ rendered=$(mktemp)
211
+ override=$(mktemp)
212
+ cp "$src" "$rendered"
213
+
214
+ if ! extract_block "$dst" "PROJECT OVERRIDE" "$override"; then
215
+ {
216
+ echo "<!-- Nội dung AGENTS.md cũ được giữ lại khi migrate sang managed sync. Hãy review và rút gọn nếu cần. -->"
217
+ echo ""
218
+ cat "$dst"
219
+ } > "$override"
220
+ fi
221
+
222
+ replace_block "$rendered" "PROJECT OVERRIDE" "$override"
223
+
224
+ if cmp -s "$rendered" "$dst"; then
225
+ SKIPPED+=("file: $dst (đã sync, không đổi)")
226
+ rm -f "$rendered" "$override"
227
+ return
228
+ fi
229
+
230
+ backup_before_sync "$dst"
231
+ mv "$rendered" "$dst"
232
+ rm -f "$override"
233
+ UPDATED+=("sync: $dst (giữ PROJECT OVERRIDE)")
234
+ }
235
+
236
+ sync_docs_index() {
237
+ local src="$TEMPLATES/docs-INDEX.md.tpl"
238
+ local dst="docs/INDEX.md"
239
+
240
+ if [[ ! -f "$dst" ]]; then
241
+ cp "$src" "$dst"
242
+ CREATED+=("file: $dst")
243
+ return
244
+ fi
245
+
246
+ local rendered content
247
+ rendered=$(mktemp)
248
+ cp "$src" "$rendered"
249
+
250
+ content=$(mktemp)
251
+ if extract_block "$dst" "PROJECT DOC STRUCTURE" "$content" || extract_section_body "$dst" "## Cấu trúc tài liệu" "$content"; then
252
+ replace_block "$rendered" "PROJECT DOC STRUCTURE" "$content"
253
+ fi
254
+ rm -f "$content"
255
+
256
+ content=$(mktemp)
257
+ if extract_block "$dst" "PROJECT MODULES" "$content" || extract_section_body "$dst" "## Modules hiện có" "$content"; then
258
+ replace_block "$rendered" "PROJECT MODULES" "$content"
259
+ fi
260
+ rm -f "$content"
261
+
262
+ content=$(mktemp)
263
+ if extract_block "$dst" "PROJECT TOKEN ROUTING" "$content" || extract_section_body "$dst" "## Feature / Keyword → Module (Token Routing)" "$content"; then
264
+ replace_block "$rendered" "PROJECT TOKEN ROUTING" "$content"
265
+ fi
266
+ rm -f "$content"
267
+
268
+ content=$(mktemp)
269
+ if extract_block "$dst" "PROJECT DOCS NOTES" "$content" || extract_section_body "$dst" "## Ghi chú riêng của dự án" "$content"; then
270
+ replace_block "$rendered" "PROJECT DOCS NOTES" "$content"
271
+ fi
272
+ rm -f "$content"
273
+
274
+ if cmp -s "$rendered" "$dst"; then
275
+ SKIPPED+=("file: $dst (đã sync, không đổi)")
276
+ rm -f "$rendered"
277
+ return
278
+ fi
279
+
280
+ backup_before_sync "$dst"
281
+ mv "$rendered" "$dst"
282
+ UPDATED+=("sync: $dst (giữ DOC STRUCTURE / MODULES / TOKEN ROUTING / DOCS NOTES)")
283
+ }
284
+
285
+ sync_plans_index() {
286
+ local src="$TEMPLATES/plans-INDEX.md.tpl"
287
+ local dst="plans/INDEX.md"
288
+
289
+ if [[ ! -f "$dst" ]]; then
290
+ cp "$src" "$dst"
291
+ CREATED+=("file: $dst")
292
+ return
293
+ fi
294
+
295
+ local rendered content
296
+ rendered=$(mktemp)
297
+ cp "$src" "$rendered"
298
+
299
+ content=$(mktemp)
300
+ if extract_block "$dst" "ACTIVE PLANS" "$content" || extract_section_body "$dst" "## Active (đang triển khai)" "$content"; then
301
+ replace_block "$rendered" "ACTIVE PLANS" "$content"
302
+ fi
303
+ rm -f "$content"
304
+
305
+ content=$(mktemp)
306
+ if extract_block "$dst" "PROJECT PLANS EXTRA" "$content" || extract_between_sections "$dst" "## Done (đã hoàn thành, chưa archive)" "## Archived" "$content"; then
307
+ replace_block "$rendered" "PROJECT PLANS EXTRA" "$content"
308
+ fi
309
+ rm -f "$content"
310
+
311
+ content=$(mktemp)
312
+ if extract_block "$dst" "ARCHIVED PLANS" "$content" || extract_section_body "$dst" "## Archived (đã Done, đã move sang plans/archive/)" "$content" || extract_section_body "$dst" "## Archived" "$content"; then
313
+ replace_block "$rendered" "ARCHIVED PLANS" "$content"
314
+ fi
315
+ rm -f "$content"
316
+
317
+ content=$(mktemp)
318
+ if extract_block "$dst" "PROJECT PLANS NOTES" "$content" || extract_section_body "$dst" "## Ghi chú riêng của dự án" "$content"; then
319
+ replace_block "$rendered" "PROJECT PLANS NOTES" "$content"
320
+ fi
321
+ rm -f "$content"
322
+
323
+ if cmp -s "$rendered" "$dst"; then
324
+ SKIPPED+=("file: $dst (đã sync, không đổi)")
325
+ rm -f "$rendered"
326
+ return
327
+ fi
328
+
329
+ backup_before_sync "$dst"
330
+ mv "$rendered" "$dst"
331
+ UPDATED+=("sync: $dst (giữ ACTIVE PLANS / PLANS EXTRA / ARCHIVED PLANS / PLANS NOTES)")
332
+ }
333
+
334
+ sync_opencode_project_guard() {
335
+ # Guard is now global-only (~/.config/opencode/plugins/).
336
+ # Just clean up any old project-level plugins from pre-v1.9 migration.
337
+ rm -rf ".opencode/plugins" ".opencode/plugin" 2>/dev/null || true
338
+ }
339
+
340
+ # === Bước 1: Tạo cấu trúc thư mục tối thiểu ===
341
+ echo "==> Tạo thư mục tối thiểu..."
342
+ ensure_dir "docs"
343
+ ensure_dir "docs/modules"
344
+ ensure_dir "plans"
345
+ ensure_dir "plans/archive"
346
+ ensure_dir ".cursor"
347
+
348
+ # === Bước 2: Sync managed templates, giữ block riêng của project ===
349
+ echo "==> Sync managed templates..."
350
+
351
+ sync_agents_md
352
+ sync_docs_index
353
+ sync_plans_index
354
+ sync_opencode_project_guard
355
+
356
+ # === Bước 3: .gitignore với patterns chuẩn ===
357
+ echo "==> Update .gitignore..."
358
+ GITIGNORE=".gitignore"
359
+ PATTERNS=(
360
+ ".cursor/.session-changes.json"
361
+ ".cursor/.docs-index-read"
362
+ ".cursor/.openmoneta-guard-loaded.json"
363
+ ".cursor/.changelog-baseline"
364
+ ".cursor/.last-test-result"
365
+ ".cursor/.ui-session.json"
366
+ )
367
+
368
+ if [[ ! -f "$GITIGNORE" ]]; then
369
+ {
370
+ echo "# OpenMoneta Dev Kit"
371
+ printf '%s\n' "${PATTERNS[@]}"
372
+ } > "$GITIGNORE"
373
+ CREATED+=("file: .gitignore")
374
+ else
375
+ ADDED=0
376
+ TO_ADD=()
377
+ for p in "${PATTERNS[@]}"; do
378
+ if ! grep -qFx "$p" "$GITIGNORE"; then
379
+ TO_ADD+=("$p")
380
+ ADDED=1
381
+ fi
382
+ done
383
+ if [[ $ADDED -eq 1 ]]; then
384
+ {
385
+ echo ""
386
+ echo "# OpenMoneta Dev Kit"
387
+ printf '%s\n' "${TO_ADD[@]}"
388
+ } >> "$GITIGNORE"
389
+ CREATED+=("update: .gitignore (thêm ${#TO_ADD[@]} patterns)")
390
+ else
391
+ SKIPPED+=("file: .gitignore (đã có patterns)")
392
+ fi
393
+ fi
394
+
395
+ # === Báo cáo ===
396
+ echo ""
397
+ echo "================================================"
398
+ echo "✅ Init OpenMoneta Dev Kit (Lean Mode) hoàn tất."
399
+ echo "================================================"
400
+ echo ""
401
+ echo "Đã tạo (${#CREATED[@]}):"
402
+ if [[ ${#CREATED[@]} -gt 0 ]]; then
403
+ for item in "${CREATED[@]}"; do
404
+ echo " + $item"
405
+ done
406
+ fi
407
+ if [[ ${#UPDATED[@]} -gt 0 ]]; then
408
+ echo ""
409
+ echo "Đã update (${#UPDATED[@]}):"
410
+ for item in "${UPDATED[@]}"; do
411
+ echo " ~ $item"
412
+ done
413
+ fi
414
+ if [[ ${#BACKUPS[@]} -gt 0 ]]; then
415
+ echo ""
416
+ echo "Backup trước khi sync (${#BACKUPS[@]}):"
417
+ for item in "${BACKUPS[@]}"; do
418
+ echo " - $item"
419
+ done
420
+ fi
421
+ if [[ ${#SKIPPED[@]} -gt 0 ]]; then
422
+ echo ""
423
+ echo "Đã skip (${#SKIPPED[@]}):"
424
+ for item in "${SKIPPED[@]}"; do
425
+ echo " - $item"
426
+ done
427
+ fi
428
+ echo ""
429
+ echo "Bước tiếp theo:"
430
+ echo " 1. Đọc AGENTS.md (quy trình 6 bước, Adaptive Planning + B6 conditional)."
431
+ echo " 2. Edit docs/INDEX.md → mô tả modules + cập nhật bảng Token Routing."
432
+ echo ""
433
+ echo "On-demand (chỉ khi user yêu cầu):"
434
+ echo " - Test (Playwright/Vitest): chạy skill 'automated-testing' khi cần."
435
+ echo " - Test bypass auth: chạy skill 'auth-bypass-testing' khi cần."
436
+ echo " - Security audit: chạy skill 'security-checklist' khi cần."
437
+ echo " - Architecture docs: tự tạo docs/architecture/ khi dự án lớn."
438
+ echo " - Project CHANGELOG: tự tạo CHANGELOG.md khi cần publish release."
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env bash
2
+ # list-affected-modules.sh — infer module slug từ .session-changes.json hoặc danh sách path.
3
+ #
4
+ # Usage:
5
+ # bash list-affected-modules.sh .cursor/.session-changes.json
6
+ # bash list-affected-modules.sh # default .cursor/.session-changes.json
7
+ # echo "apps/landing/src/x.ts" | bash list-affected-modules.sh - # đọc paths từ stdin
8
+ #
9
+ # Output: 1 module slug per line (sorted, unique). Format: <slug>\t<docs_path>\t<source_path>
10
+
11
+ set -euo pipefail
12
+
13
+ INPUT="${1:-.cursor/.session-changes.json}"
14
+
15
+ read_paths() {
16
+ if [[ "$INPUT" == "-" ]]; then
17
+ cat
18
+ elif [[ -f "$INPUT" ]]; then
19
+ if command -v jq >/dev/null 2>&1; then
20
+ jq -r '.changes[]?.path // empty' "$INPUT"
21
+ else
22
+ grep -oE '"path"[[:space:]]*:[[:space:]]*"[^"]+"' "$INPUT" | sed 's/.*"\([^"]*\)"$/\1/'
23
+ fi
24
+ else
25
+ echo "[!] Input không tồn tại: $INPUT" >&2
26
+ return 1
27
+ fi
28
+ }
29
+
30
+ infer() {
31
+ awk -F/ '
32
+ # Skip empty
33
+ NF == 0 { next }
34
+ # Whitelist không phải code module
35
+ /^docs\// { next }
36
+ /^plans\// { next }
37
+ /^tests\// { next }
38
+ /^scripts\// { next }
39
+ /^infra\// { next }
40
+ /^logs\// { next }
41
+ /^\.cursor\// { next }
42
+ /^node_modules\// { next }
43
+ $0 == "AGENTS.md" { next }
44
+ $0 == "CHANGELOG.md" { next }
45
+ $0 == "README.md" { next }
46
+ $0 == ".gitignore" { next }
47
+ $0 == ".env.example" { next }
48
+
49
+ # apps/<x>/...
50
+ /^apps\// && NF >= 2 {
51
+ slug = "apps-" $2
52
+ print slug "\tdocs/modules/" slug "\tapps/" $2
53
+ next
54
+ }
55
+ # packages/<x>/...
56
+ /^packages\// && NF >= 2 {
57
+ slug = "packages-" $2
58
+ print slug "\tdocs/modules/" slug "\tpackages/" $2
59
+ next
60
+ }
61
+ # src/modules/<x>/...
62
+ /^src\/modules\// && NF >= 3 {
63
+ print $3 "\tdocs/modules/" $3 "\tsrc/modules/" $3
64
+ next
65
+ }
66
+ # src/<x>/... (single-repo, không có modules/)
67
+ /^src\// && NF >= 2 && $2 != "modules" {
68
+ print $2 "\tdocs/modules/" $2 "\tsrc/" $2
69
+ next
70
+ }
71
+ ' | sort -u
72
+ }
73
+
74
+ read_paths | infer